# dsh-dcp Deterministic context-compaction backend for dsh (DeepSeek Harness): **context compaction without an LLM call**, works out of the box. **Requires dsh >= 0.1.2-rc.1** — this plugin targets the dsh RC/stable line only (CI and releases resolve the newest of the `latest`/`next` dist-tags at runtime). **The alpha line is no longer supported.** > [简体中文](README.md) · **English** ## Why dsh compacts conversation context by default with `compaction-basic`, which asks an LLM to re-summarize older messages on every compaction — costly, slow, and non-deterministic. dsh-dcp is a pure-code port of the ideas behind [opencode-dcp](https://github.com/Opencode-DCP/opencode-dynamic-context-pruning) (dedup, error cleanup, "technical summary instead of prose"): - **Zero LLM calls**: compaction itself costs no extra tokens - **Deterministic**: identical input always yields identical output - **CJK-friendly**: verbatim user text / paths / commands / errors, priced at real CJK density - **Inherits all official safety**: triggers, retained tail, transaction locks, tool-pairing — dsh's own machinery, only the summarizer is replaced ## Effects ### vs. the official compaction-basic | | compaction-basic | dsh-dcp | |---|---|---| | Summarization | LLM rewrite per compaction | deterministic code extraction | | LLM calls per compaction | 1 | **0** | | Determinism | may differ run to run | identical input → identical output | | Summary content | semantic | verbatim hard facts (paths/commands/errors/todos/user text) | | Chinese | model re-transcribes | kept verbatim + CJK-aware pricing | | Triggers/retention/overflow/safety | official | **inherited, identical** | | Checkpoint format | official | compatible (mutually mergeable) | It also borrows the dedup / error-purge / `/dcp` / technical-summary ideas from [opencode-dcp](https://github.com/Opencode-DCP/opencode-dynamic-context-pruning), re-implemented against dsh's compaction seam — that one serves opencode, this one serves dsh. ### CJK adaptation Content is kept verbatim (no re-transcription into English); tokens are priced at real CJK density (~2 chars/token for Chinese/Japanese/Korean/full-width) instead of the host's flat 4 chars/token that underestimates Chinese — so CJK sessions get a budget that reflects real cost, and checkpoints stay information-dense. ### Real dsh session ~80k tokens of history → ~700-token checkpoint (**~100x**), zero LLM calls; cache-hit rate is barely affected (any backend pays one "cold request" right after a compaction). A checkpoint produced on a real session (Chinese content kept verbatim): ``` ## Primary Request and Intent - 帮我把登录页的重定向 bug 修掉 ## Files and Code - /app/src/auth/login.ts — W×1 R×1 ## Errors and Fixes - bash: FAIL src/auth.test.ts ## Pending Jobs - add regression test ## Critical Context - dsh-dcp 确定性压缩了 12 条消息 / 8 次工具调用(未调用 LLM 摘要) ``` ## Not in scope - **No semantic summarization**: it preserves facts that appeared, it does not "understand" code. Need deep semantic checkpoints? Stick with the official `compaction-basic` - **Things dsh already does, deliberately not re-implemented**: - tool-result pruning (`compaction-tool-result-pruner`, deterministic by size) - trigger policy, retained tail, overflow recovery (inherited from official; this plugin only adds the round-interval trigger, see below) - `/compact` command, UI checkpoint cards (shipped with dsh) ## Install **Recommended: pair it with our dsh-tui-pi** (the TUI already depends on dsh-dcp): ```bash npm i @aiwayds/dsh-tui-pi dsh plugin add @aiwayds/dsh-dcp # activates dcp; the bundle auto-mounts ``` **Standalone:** ```bash npm i @aiwayds/dsh-dcp npx dsh-dcp-setup # safe: date-stamped backup → append-only → idempotent checks ``` > dsh-dcp plugs into dsh's compaction seam and only affects profiles that > mount it. The web profile does not bundle the TUI, so it keeps the official > backend and is unaffected. ## Uninstall **Bundle path** (`dsh plugin add`, or listed under `bundles`): ```sh dsh plugin --profile remove @aiwayds/dsh-dcp ``` The host reconciles automatically: the bundles entry is spliced, the patch layer drops with the package, and the stock LLM summarizer (`compaction-basic`) re-enables. **Setup-script path** (the patch block `npx dsh-dcp-setup` wrote): ```bash npx dsh-dcp-setup --remove # default home patch npx dsh-dcp-setup --remove --profile tui # a named profile npx dsh-dcp-setup --remove /path/to/cordis.patch.yml ``` `--remove` strips only the setup-written mount block (including any config you tuned), with the same date-stamped backup before modifying; hand-written mounts are left alone; the file is deleted when nothing else remains. Two WARN lines matter: a leftover `compaction-basic` disable entry keeps the stock summarizer off (remove it by hand if it was only for dsh-dcp), and another dsh-dcp mount in the same file is untouched. Removing the package without the reverse step leaves the mount pointing at the vanished absolute entry path, and the profile boot fails with module-not-found. ## /dcp command | Command | Effect | |---|---| | `/dcp` | status: config, compaction count, tokens saved | | `/dcp compact` | compact now (zero LLM) | | `/dcp set ` | adjust a knob for this session, with a persist hint | Settable: `dedup`, `purgeErrors`, `maxItems`, `maxItemChars`, `maxSummaryTokens`, `language`, `tokenEstimate`, `thresholdRatio`, `roundInterval`, `notice`. The `/dcp` status also lists every session that has compacted (subagents included): `per-session: session-1 (2 compactions, ~444 tokens), child (1 compaction, ~22 tokens)`. Compactions count per session; disposed sessions (one-shot subagents included) fall out of the overview automatically, and the list is capped at the first 10 sessions (`+N more` for the rest) so the status stays one line. ## Triggers | Trigger | When | Notes | |---|---|---| | Pressure | before every step | tokens ≥ `thresholdRatio` (inherited upstream default 0.8; this plugin's bundle mounts 0.7 — see config table) × context window | | Overflow recovery | on a provider context-window error | inherited | | **Round interval** | every `roundInterval` assistant messages | added by this plugin; one round = one LLM roundtrip (each tool-iteration response counts, so one-shot subagents trigger too). **Default 50**: first compaction after message 50, then every 50 more (100, 150, …); any compaction (pressure included) restarts the clock. Fires at the first idle boundary after the count is reached (below the pressure threshold too). `0` disables; requires the default `auto: true` | | Manual | `/dcp compact`, `/compact` | anytime | - **Subagents are covered**: in-process subagents (including continuable and one-shot children) dispatch through the same events, so pressure/overflow/round triggers count and fire per child session independently. The round trigger counts assistant messages, so a one-shot subagent whose whole run is a single turn (many tool iterations) triggers too. - **Visibility**: after every trigger event a one-line notice row (`dcp: compacted N history items (~X tokens, trigger)`) is appended to the session; frontends render it as a collapsed row. Note the row also rides the model request context (~15–25 tokens per compaction), and it is **on by default since 0.4.0** — disable with `notice: false`. `/dcp` stats count every committed region (a pressure retry loop may commit several). ## Configuration All optional, defaults work out of the box: | Key | Default | Meaning | |---|---|---| | `thresholdRatio` | 0.8 | pressure trigger (inherited upstream compaction-basic default 0.8; this plugin's bundle patch mounts 0.7, recommended for CJK-heavy sessions) | | `roundInterval` | 50 | compact every N assistant messages (one LLM roundtrip) (0 disables). Default 50: 50, 100, 150… — the clock restarts after every compaction | | `notice` | `true` | append the one-line compaction notice to the session | | `language` | `zh` | summary language; `zh` also enables Chinese error/"待办:" detection | | `tokenEstimate` | `cjk` | CJK (zh/ja/ko/full-width) at ~2 chars/token; `ascii` matches the host | | `dedup` | `true` | annotate repeated tool calls | | `purgeErrors` | `true` | collapse stale errors into one note | | `maxItems` / `maxItemChars` | 10 / 200 | summary density | | `maxSummaryTokens` | 2048 | summary token budget | > **Upgrade note (0.5.0)**: the `roundInterval` counter switched from completed turns to assistant messages — the same value now triggers more often (a single turn usually contains several assistant messages). ## Bundled skill The package registers a `dsh-dcp` skill (`skills/dsh-dcp/SKILL.md`, served via `ctx.skills.registerProvider`): ask the agent to tune compaction, configure dcp, or debug compaction behavior and the guide loads automatically — it carries an interactive `ask_user_question` tuning wizard (collect the expectation first, then map it to keys) and the persistent recipe for the `config:` section of the cordis.patch.yml mount block. ## Design reference - [Opencode-DCP/opencode-dynamic-context-pruning](https://github.com/Opencode-DCP/opencode-dynamic-context-pruning) - dsh official compaction seam: `docs/subsystems/compaction.md` (deepseek-harness) ## Development ```bash npm install && npm test # 65 tests: extractor/compaction/command/config/triggers/setup ``` ## License MIT