Bundle
dsh-context-triage
会话上下文分诊插件:为 DeepSeek Harness (dsh) 自动筛查过期与冗余的上下文内容,以判决式处理节约 token 预算。
- Source
- JohnXu22786
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 5 days ago
Readme
[简体中文](README.zh.md)
# dsh-context-triage
A session context triage plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) that automatically manages context volume during long sessions: it identifies and handles stale, duplicate, failed, oversized, and low-value message content to save token budget and curb context bloat.
## Features
- **Deterministic, zero model dependency**: all verdicts and rewrites are produced by heuristic rules—no LLM calls, no extra services; behavior is predictable and testable offline.
- **Five Screeners**, each independently switchable:
| Screener | Verdict rule | Handling action |
| --- | --- | --- |
| Stale output `staleOutput` | Tool call pairs more than N turns after the latest user message, with results still complete | Call block rewritten to an archive summary, result block removed |
| Repeated call `repeatedCall` | Call pairs with the same tool and same arguments (JSON key order irrelevant) | Only the most recent is kept; older call pairs are cut |
| Failed call `failedCall` | Stale error results (`isError`) | Input arguments replaced with a failure stub (prevents leakage / saves space), error text kept and trimmed |
| Oversized block `oversizedBlock` | Tool result text exceeds the character cap | Head/tail trimmed, middle annotated with the omitted amount |
| Stale reasoning `staleReasoning` | Reasoning blocks beyond the retention turns; kept blocks exceeding the length cap | Removed; or trimmed (within the reserve region only trim, never delete) |
- **Reserve area (`reserve`)**: content within the most recent N user turns is never touched, avoiding disruption of context the model is currently using.
- **Exemption list (`exempt`)**: two-dimensional tool-name and file-path glob exemption (protects stateful tools like `task`/`skill`/`todowrite`/`todoread`/`write`/`edit`/`batch` by default).
- **"Worth it" check**: any archive/stub action requires the rewrite to be strictly smaller than the original, ruling out changes that cost more; call-pair-level actions (archive = rewrite + cut) take effect or are abandoned as atomic groups.
- **Audit report**: every triage outputs structured statistics—counts and savings per reason, per-item details, and suggested compression ranges; visible through the tool, the command, and the CLI.
- **Native integration with the dsh compression seam**: implements `ctx.compaction` (`CompactionEngine`); auto-pressure, context overflow, manual, and forced region compression all go through the official harness mechanism. Replacement summary messages use a checkpoint source recognized by any backend-agnostic consumer.
## Installation and mounting
The plugin is distributed as a **bundle**: `dsh.bundle.patch` in `package.json` points to `cordis.patch.yml` (the patch inserts the plugin line; config can be overridden on the line).
### Installing in DSH
```bash
dsh plugin --profile demo add github:JohnXu22786/context-pruner
```
Option 1 (mount from a local directory, recommended):
```bash
dsh plugin --profile web add link:/absolute/path/context-pruner
```
Option 2 (manual patch): merge the entry in `cordis.patch.yml` into the profile's `cordis.patch.yml`, or launch it directly as an overlay:
```bash
dsh web --patch ./cordis.patch.yml
```
Option 3 (git source):
```bash
dsh plugin --profile web add "github:your-repo/context-pruner#main"
```
After mounting, use `dsh --profile web --dump-config` to check whether the plugin line reaches the startup tree.
> Note: `ctx.compaction` allows only one provider per context. If your profile already loads another compression implementation (e.g., the built-in base compression backend), disable one of them via the patch's `disabled: true`—they cannot coexist.
## Interface
### Entry point (manifest)
| Item | Value |
| --- | --- |
| Package name | `dsh-context-triage` (`dsh.bundle.patch` in `package.json` declares the bundle) |
| Main entry | `lib/index.js` (`main`/`exports`; `./core` and `./dsh` subpaths available for embedders) |
| Plugin name | `context-triage` |
| Dependency injection | `tools` (required); the command registry is optionally probed via `ctx.get('commands')`, silently skipped if absent |
| Config | Exports `Config` (Schemastery Schema); defaults in the config table below |
The plugin is a functional plugin: it exports `name` / `inject` / `Config` / `apply(ctx, config)`, called by cordis after validating config and filling defaults.
### Provided extension points
| Extension point | Description |
| --- | --- |
| Service `ctx.compaction` | `TriageCompactionEngine extends CompactionEngine`, implementing `compactIfNeeded` (auto-pressure / overflow), `compactNow` (manual idle compression), `compactRegion` (forced region compression) |
| Tool `triage_history` | Model-visible; parameter `dryRun?: boolean`. Runs one triage and returns the audit report to the model; with `dryRun=false` and a worth-handling region present, applies it directly |
| Command `/triage` | Human command, bypasses the model; outputs the audit report and applies worthwhile handling |
### How a compression transaction lands
Triage results are written to the session log as a standard compression transaction (append-only; history is not rewritten):
1. `compaction/start` (holds the lock until the paired `compaction/end`)
2. `compaction/summary` (summary content, masked regions, masked seq list, and heuristic token cost; `llmStreamCall` absent = non-model summary)
3. `user/message` + `surfaceOp: { op: 'replace', start, end }` + `sourceEventSeqs` (replacement summary message, `source` is the checkpoint source)
4. `compaction/end`
The model-visible history is derived from the session log; after the replacement, `deriveMessages()` naturally yields `[summary message, ...retained content]`—no in-place modification of historical records is needed.
## Configuration
All fields have defaults; only override what you want to adjust. Invalid values (negative turns, out-of-range ratios, etc.) throw at load time. Example:
```yaml
# cordis.patch.yml
- insert:
id: context-triage
name: dsh-context-triage
config:
budget:
contextTokens: 200000 # lower for smaller-context models
softRatio: 0.6
screeners:
staleOutput: { turns: 5 }
staleReasoning: { enabled: false }
exempt:
tools: [task, skill, write, edit]
```
| Field | Default | Description |
| --- | --- | --- |
| `enabled` | `true` | Master switch |
| `reserve.turns` | `3` | Reserve area: content within the most recent N user turns is not processed |
| `budget.contextTokens` | `1000000` | Estimated context window (tokens); determines the pressure ratio |
| `budget.softRatio` | `0.7` | Usage above this ratio → auto-compression triggers (pressure) |
| `budget.hardRatio` | `0.9` | Usage above this ratio → pressure report marked forced (hard) grade |
| `budget.minSavingsTokens` | `2000` | No action when estimated savings are below this |
| `screeners.staleOutput.turns` | `8` | Call pairs more than N turns after the latest user message are archived |
| `screeners.repeatedCall.enabled` | `true` | Repeated calls cut |
| `screeners.failedCall.turns` | `4` | Failed-call handling threshold (turns) |
| `screeners.failedCall.errorKeepChars` | `400` | Error text kept for failed calls (chars) |
| `screeners.oversizedBlock.capChars` | `6000` | Tool result text cap (chars); beyond it, head/tail trimmed |
| `screeners.oversizedBlock.headChars` / `tailChars` | `800` / `400` | Head/tail length kept during trimming |
| `screeners.staleReasoning.keepTurns` | `3` | Keep reasoning blocks within the most recent N user turns |
| `screeners.staleReasoning.maxBlockChars` | `2000` | Length cap for kept reasoning blocks; beyond it, trimmed |
| `exempt.tools` | `[task, skill, todowrite, todoread, write, edit, batch]` | Exempt tool names |
| `exempt.filePatterns` | `[]` | Exempt path globs (match call arguments `filePath`/`path`), e.g. `['**/*.lock']` |
| `summary.capChars` | `20000` | Compression summary char cap |
| `summary.headRatio` | `0.4` | Head retention ratio when trimming the summary |
## Local experience (no dsh required)
`src/core` is a framework-agnostic engine with an offline replay CLI; it runs directly against a JSONL session file:
```bash
npm install
npm run build
node lib/cli/replay.js examples/session.sample.jsonl --config examples/demo.config.json
node lib/cli/replay.js examples/session.sample.jsonl --config examples/demo.config.json --show-transcript
```
Replay format (one JSON event per line; seq determined by line order):
```jsonl
{"type":"user/message","text":"项目构建失败了"}
{"type":"assistant/message","reasoning":"…","calls":[{"id":"c1","name":"bash","arguments":"{\"cmd\":\"npm run build\"}"}]}
{"type":"tool/result","callId":"c1","text":"…","isError":false}
```
Sample output in [examples/report.example.md](examples/report.example.md); tests cover all screeners, merge priorities, atomic groups, audit consistency, and end-to-end replay (`npm test`).
## Design trade-offs
- **Token estimation is heuristic**: CJK ≈ 1 token/char, others ≈ 4 chars/token; used only for pressure verdicts and audit statistics, not for billing. Configure `budget.contextTokens` per your actual model window.
- **Rewrites always yield net savings**: archive summaries, failure stubs, and trimming all compare the sizes before and after the action; unattractive findings are automatically abandoned. Call-pair actions are judged as atomic groups, so there is no half-done state like "summary kept, result split."
- **Prompt caching**: compression changes the message sequence, invalidating the prompt cache prefix after that point. In long sessions, saved tokens usually vastly outweigh cache recomputation cost; for pay-per-request providers (no cache billing) it is pure gain.
- **Never touches user input**: the oversized-block screener only acts on tool results; user messages are never rewritten unless the whole reserve region covers them.
## License
Released under the [MIT License](LICENSE).Install
dsh plugin --profile web add github:JohnXu22786/context-pruner
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-context-triage from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.