Preface

In DeepSeek Harness (DSH), the large blocks of text returned by tool calls enter the model context. Harness truncates by default based on size: it keeps the beginning and end, discarding the middle. Agent hosts like Codex, pi, etc., often adopt similar practices.

This truncation ignores the content’s shape. When test output shows 5,000 passing lines with 3 failures in the middle, head-tail truncation often leaves only the passing records, causing the model to misjudge that all tests passed. unclecode’s toolshrink addresses this issue: it first identifies the output pattern, then retains the informative parts, rather than simply slicing by position.

What is this

toolshrink is a DSH plugin that can also be used standalone as a Node.js library. Maintained by unclecode, the repository is located at: github.com/unclecode/toolshrink. The project is categorized as “Model Inference” in the SkillHub community directory, with approximately 10 GitHub stars.

One-line positioning: Cut large agent tool output by what it means, not by where it was cut. (Compress by meaning, not by truncation point.) The plugin includes 13 content-aware reducers; when none match, it falls back to size-based truncation, ensuring results always stay within budget.

Differences from Default Truncation

The README provides a vitest example: input of 31,958 characters, 805 lines, with a budget of 2,000 characters.

Method Output Size What the Model Sees
Head+Tail Truncation 1,904 characters Primarily the summary
toolshrink 255 characters Which tests failed, reasons, line numbers, and summary

Removed content is marked at the output end, e.g., ... 15,903 characters, 401 lines omitted .... If spill storage is configured, the full original text is written to disk with a locator, preventing silent loss.

Core Feature: 13 Cuts

Each cut identifies a text pattern and is tried in order; the first matching cut executes. If none match, the size fallback is used. All cuts follow four rules: no returning half-lines, no splitting UTF-16 surrogate pairs, explicitly marking removed amounts, and ensuring second calls do not change the result.

Cut Identifies Retains Discards
diff git diff, patch Changed lines, file and hunk headers, 1 line of context on both sides Unchanged context
json Single JSON values Structure, 3 samples per long array, 5 keys per wide object, counts Repeated records
tests vitest, jest, pytest, cargo test, go test Failed items and descriptions, summary Passing tests
build tsc, cargo, gcc, webpack, esbuild Errors and warnings with code frames, summary Build progress
stacktrace Node, Python, Java, Ruby stacks Messages and user code frames Dependency library frames (count marked)
log Logs with timestamps Errors and warnings with preceding lines, ending Regular lines
tree find, ls -R, file lists Directory structure, 8 entries per directory, counts Remaining entries in crowded directories
repeat Retry storms, progress spam 2 samples per pattern + omission explanation Consecutive near-duplicate lines
lint eslint, ruff, clippy Count and sample locations per rule, worst file Same rule repetitions
install npm, pip, pnpm, cargo installs Summary, versions, deprecations, vulnerabilities, errors Download progress
csv CSV, TSV, pipe tables Header, first 5 rows, last 2 rows, row and column counts Middle rows
gitlog git log (two formats) Latest 15 commits, total, authors and counts Older commits
size Fallback bash: end; grep/read: start; unknown: head and tail Rest (count marked)

Adding a new cut requires only a file implementing the shared interface; no need to fork the entire project.

Installation and Enablement

DSH uses an “everything is a plugin” architecture; SkillHub (skillhub.cn) is a community skills directory for Chinese users, with no official affiliation to DeepSeek or High-Flyer. Before installation, it’s recommended to browse the repository source and confirm the MIT license (see package.json). Plugins run with the current dsh process permissions.

Install to the web profile with one command:

dsh plugin --profile web add github:unclecode/toolshrink

The package includes a dsh.bundle manifest, automatically mounting on next start with a default character budget of 50,000. Adjustments can be made in the user-level ~/.dsh/cordis.patch.yml:

- id: toolshrink
  config:
    maxChars: 20000
    log: /tmp/toolshrink.log

For local development, clone the repo, run npm install && npm run build, then mount the adapter file via insert:

- insert:
    - id: toolshrink
      name: /path/to/toolshrink/adapters/harness/toolshrink.mjs
      config:
        maxChars: 50000        # Trigger compression if exceeded (default 50000)
        maxLines: 2000         # Or if line count exceeded (default 2000)
        maxLineChars: 0        # Single-line length limit, 0 disables (default 0)
        disable: [json]        # Skip specified cuts (default none)
        spillDir: ~/.dsh-toolshrink   # Directory for full original text
        log: /tmp/toolshrink.log      # Log one line per cut; omit to suppress

Example log line format: bash 64151 -> 2942 via tree+size.

Typical Usage

As a Library

Current version is 0.1.0 (ESM, main points to ./dist/index.js).

import { shrink, FileSpillStore } from 'toolshrink'

const out = shrink(bigText, { tool: 'bash', command: 'npm test' }, {
  budget: { maxChars: 20_000 },
  spill: new FileSpillStore({ dir: '/tmp/spills' }),  // Optional
})

out.content   // Text to pass to the model
out.reduced   // False if input didn't exceed budget
out.strategy  // e.g., "tests", "diff+size", "size:tail", "none"
out.note      // Human-readable one-line explanation
out.stats     // inputChars, outputChars, keptLines, droppedLines, etc.

The second parameter hint is optional: tool affects size truncation direction, command helps identify tests and diffs, path helps identify JSON and logs.

Custom Cuts

A cut file exports three members by default, with the filename as the cut name:

// mycut.mjs
export default {
  name: 'mycut',
  detect(text, hint) {
    return hint.command?.startsWith('kubectl') ?? false
  },
  reduce(text, hint, budget) {
    const content = text.slice(0, budget.maxChars)
    return {
      content,
      reduced: true,
      strategy: 'mycut',
      note: 'kept the part I know matters',
      stats: {
        inputChars: text.length, inputLines: 0,
        outputChars: content.length, outputLines: 0,
      },
    }
  },
}

Load and use:

import { shrink, loadReducers } from 'toolshrink'

const mine = await loadReducers('/path/to/my-cuts')
shrink(text, hint, { extra: mine })           // Tried before built-in cuts
shrink(text, hint, { only: ['tests', 'diff'] }) // Restrict and order
shrink(text, hint, { disable: ['json'] })       // Skip specified cuts

Spill: Full Original Text Recoverable

After enabling the spill store, the full original text is written to disk before compression, with a locator appended to the compressed text, e.g.:

[full output saved as spill:bash-d63d2aebb643: directories sampled to 8 entries each]

store.load('spill:bash-d63d2aebb643') can restore the original text at the byte level. The default file store cleans up after 24 hours; the storage backend can be replaced.

Use Cases and Notes

Suitable For

  • Developers who frequently call shell, test, build, lint, and other tools in DSH or custom agents, with outputs often exceeding context budgets.
  • Scenarios requiring auditable truncation (explicit omission amount + optional spill) rather than silent data loss.

Usage Notes

  • When no cut matches, it still falls back to size, behaving similarly to “truncation by position”; improve recognition via hint and custom cuts.
  • README notes: With a 3,000-character budget, a 60,000-character find result truncated by head leaves the model seeing the omission marker and proactively switching to aggregate queries—this is by design, relying on the model to read the marker.
  • Installation and running permissions match the dsh process; spillDir writes to local disk, so pay attention to path and disk usage.
  • The repository TODO lists planned cuts (e.g., semantic, sql) not yet implemented; do not treat them as existing capabilities.

Conclusion

toolshrink changes Agent tool output truncation from “byte-slicing” to “pattern-preserving”: tests keep failures, diffs keep changes, logs keep errors. For DSH users, a single dsh plugin add mounts it; for finer control, adjust YAML or write custom cuts.