Skip to main content

dsh-output-retention/packages/util/output-retentionOfficial

175.3kStars19.0kForks0Issues752Watchers

Zero-dependency bounded-retention primitive: ItemRetainer/TextRetainer + neutral notice helpers (what did we keep, what did we omit)

Evidence3/5methodologySourceInstallMaintenanceDSH versionSecurity scan
Machine-auditedInstall commandRepo verifieddsh-plugin topicLicenseREADMEAI wiki

ⓘ This plugin is a sub-package of the deepseek-ai/deepseek-harness monorepo — stars and activity count the whole repository.

Language
TypeScript
License
MIT
Branch
master
ai-agentscordisdshdsh-plugin

Install

cmdweb profile
$ dsh plugin --profile web add npm:@deepseek-ai/dsh-output-retention

Run the command above in your terminal to install this plugin via the dsh CLI. You can switch Profile in the top-right corner. New to dsh? Read the beginner tutorial

Install via your agent

Install the DeepSeek Harness plugin deepseek-ai/deepseek-harness/packages/util/output-retention for me: review the repository at https://github.com/deepseek-ai/deepseek-harness first, then run the install command and verify the plugin loads successfully.

Paste this instruction to the DSH Web GUI assistant — it will install and verify for you.

English | 中文

A dependency-light retention library: bounded model-facing output for tools that must cap how much context they return. A caller feeds items or text chunks into a bounded object, then gets the retained content plus exact omission metadata.

The library owns only the mechanical question "what did we keep, and what did we omit?". Tool-specific code keeps its business semantics: file grouping, line numbering, exit codes, provider error states, per-line preview truncation, spill files, and the model-facing prose. This is the boundary the Agent Note draws.

It is a library, not a service or plugin: no ctx, registers nothing, emits no events. The only state is per-retainer (one accumulation), never cross-call. Tool packages import it directly.

API

import {
  ItemRetainer, TextRetainer,
  describeOmitted, formatRetentionNotice,
} from '@deepseek-ai/dsh-output-retention'
import type {
  Omitted, PushDecision, RetainedItems, RetainedText,
  ItemRetentionStrategy, TextRetentionStrategy, RetentionNotice,
} from '@deepseek-ai/dsh-output-retention'
ExportRole
ItemRetainer<T>Bounds ordered logical units (paths, grep matches, sources). head only. push() → PushDecision; finish() → RetainedItems<T>.
TextRetainerBounds a byte-oriented text stream. head / tail / headTail, UTF-8 boundaries preserved at finish(). push() → PushDecision; finish() → RetainedText.
describeOmitted(omitted, unit)Standardized omission clause (exact prints a count; unknown does not).
formatRetentionNotice(notice, recovery)Joins the standardized omission clause with the tool's own recovery guidance.
Omittednone / exact / unknown — how much was omitted.
PushDecision{ kept, truncated } — the per-push retention result.

Resource Modes

The two retainers are separate names, not one generic collector, because they differ in resource model.

  • ItemRetainer bounds ordered logical units. A search tool can collect a full result set for spill-file recovery while retaining only the first maxItems for the model-facing preview. The omission count is exact because the caller keeps feeding every observed item.
  • TextRetainer bounds byte-oriented text. head, tail, and headTail preserve UTF-8 boundaries at finish(); headTail is the shape dsh-spill-policy uses to build a bounded preview around a spill-file notice.

truncated is a budget fact, never "incomplete"

truncated means the retainer omitted otherwise-available content because of a budget. It does not mean the upstream was incomplete. Permission failures, skipped binary files, provider partial failures, unreadable candidates, and invalid UTF-8 stay in tool-domain fields — never folded into truncated. Conflating the two is the bug this library's naming most invites; keep them separate.

Bytes, not characters

Text caps and omittedBytes count bytes, for process/body safety (a child's pipe and an HTTP body are byte streams). A chunk that straddles a codepoint is handled: finish() trims a partial codepoint at each cut so the returned text never introduces a replacement char at the boundary, and the two sides are decoded separately so a codepoint is never reconstructed across the omitted middle. Character- or line-level preview budgets are a separate, tool-owned concern.

Tool mappings

Current retention consumers use these mappings:

ToolRetainer & strategyNotes
globItemRetainer<FsGlobEntry>, headCollect the full sorted path list for a spill file while retaining the first page inline. Path mapping, skipped candidates, and incomplete stay outside.
grepItemRetainer<FlatGrepMatch>, headCollect matches for a spill file while retaining the first page inline. Per-match preview truncation, grouping, sorting, and incomplete stay outside.
bashTextRetainer, tail or headTailExecutor still owns spill files, exit status, signal, timeout, and background jobs.
web_fetchTextRetainer, head or headTailProvider/resource caps stay provider facts; the retainer supplies only retained text and omission metadata.
web_searchItemRetainer<WebSearchSource>, headStandardizes the "sources capped" notice when providers return more sources than the model-facing result should include.

read remains outside this generic library. Its read-render helper owns a file-specific pagination contract — offset/limit, line numbers, totalLines, offset-out-of-range errors, per-line preview truncation, and a byte cap over the selected window — which is a line-window renderer. A single Omitted count cannot represent both sides of that window.

Usage shape

// glob: keep the first page inline while still collecting the full list for spill.
const retainer = new ItemRetainer<FsGlobEntry>({ kind: 'head', maxItems: globMaxResults })
const allEntries: FsGlobEntry[] = []
for await (const entry of candidates) {
  allEntries.push(entry)
  retainer.push(entry)
}
const { items, truncated, omitted } = retainer.finish()

// bash: keep a head + tail, read to process exit.
const out = new TextRetainer({ kind: 'headTail', headBytes: headCap, tailBytes: tailCap })
child.stdout.on('data', (chunk: Buffer) => { out.push(chunk) })
const { text, omittedBytes } = out.finish()

// A footer: the library standardizes the omission clause; the tool owns recovery words.
const footer = formatRetentionNotice(
  { scope: 'grep', strategy: 'head', unit: 'items', limit: grepMaxMatches, kept: items.length, omitted },
  ({ kept }) => `Results capped at ${kept}. Narrow the pattern, path, or include to see more.`,
)

Model Experience

Indirectly, through tool consumers that render retained content and omission metadata.

KV Cache effect

No direct invalidation; the named consumer owns any request-prefix changes.

Known Limitations and Deferred Work

  • Item retention supports head only — tail, head/tail, pagination, grouping, and provider-completeness semantics remain tool-owned.
  • Text retention is byte-oriented — line and character windows such as read pagination require a separate renderer, and a cut may discard partial UTF-8 boundary bytes to keep returned text valid.

Read the usage guide →

Install steps, key points, FAQ and compatibility for this plugin — auto-derived from indexed fields.

Listing badge

Listed on deepseek-plugin.org
[![Listed on deepseek-plugin.org](https://img.shields.io/badge/listed_on-deepseek--plugin.org-007EC6)](https://deepseek-plugin.org/plugins/deepseek-ai/deepseek-harness/packages/util/output-retention)

Paste this markdown into your GitHub README to link back to this listing. The badge only states the listing — not a security endorsement.

← Back to plugin directory