Files
deepseek-harness/packages/compact/compact
Hypatia May e45053f0f5 feat(compact): compaction capability seam — abstract CompactService interface
Adds the @deepseek-ai/dsh-compact interface package: the abstract
CompactService (ctx.compact) with compactIfNeeded / compactRegion, the
compact/* session-event types via SessionEventMap declaration merging, and the
capability-seam RFC. Wires the package into the three root tsconfigs and the
cordis catalog. A backend implementation lands separately.
2026-06-22 15:15:27 +08:00
..

@deepseek-ai/dsh-compact

The compaction seam: an abstract CompactService (ctx.compact) defining WHAT compaction does — decide when history is too large and summarize an older range into a single surface node — without saying HOW.

This package is the interface tier of the compaction capability, split so each concern evolves (and swaps) independently:

Package Role
@deepseek-ai/dsh-compact (this) the interface: abstract service + compact/* events + CompactionResult
@deepseek-ai/dsh-compact-basic a backend: char/4 estimation + token-budget retention + llm.stream() summarization
@deepseek-ai/dsh-tool-compact (deferred) the model-facing /compact tool over ctx.compact

Unlike the bash seam, this interface depends on @deepseek-ai/dsh-session and @deepseek-ai/dsh-llm — the contract's verbs are defined over a Session and its output is the ContentBlock vocabulary, so they cannot be expressed without naming those packages. That deviation from the "interface depends only on cordis" guidance is intentional and recorded in the compaction capability-seam RFC.

Service API (ctx.compact)

Both methods are abstract — the backend owns the entire strategy (token estimation, retention policy, event sequencing, summarization).

Member Semantics
compactIfNeeded(session, systemPrompt?, model?) Estimate the history size; if over the backend's threshold, compact an older range via compactRegion, keeping recent context intact. Returns the CompactionResult, or null if nothing needed compacting.
compactRegion(session, start, end, model) Forcibly summarize surface nodes [start, end] (inclusive seqs) into a single replacement node. Throws if a compaction is already in progress, if start/end aren't surface nodes, or if start > end.

Surface contract

SurfaceEventType is a closed union — only user/message, assistant/message, tool/result, context/message, and steering/message may carry surfaceOp. A compact/* event therefore cannot appear on the surface. A successful compaction instead:

  1. appends compact/start (log-only) — acquires the lock,
  2. summarizes the range,
  3. appends compact/summary (log-only) — provenance: summary, range, shadowed seqs, token count,
  4. appends compact/end (log-only) — releases the lock,
  5. appends a single user/message with surfaceOp: { op: 'replace', start, end } carrying the summary — the only surface mutation.

deriveMessages() then renders the summary as a user-role message followed by the retained nodes. The shadowed events remain in the raw log, so replay is deterministic.

Blocking

Compaction is serialized via a log-recorded lock: compactRegion refuses to start if the last compact/start has no matching compact/end after it. The lock is the log (not an in-memory mutex), so it survives replay and a persistence backend can detect an orphaned compact/start on reload. compact/end is appended even when summarization throws, so a failure can never wedge the lock.

Events

The compact/* events extend SessionEventMap (merge-extensible) via declaration merging — they are session events, not cordis Events:

Event Payload On surface?
compact/start { turn } no (log-only)
compact/summary { summary, compactedRange, compactedEventSeqs, tokenCount } no (log-only)
compact/end { turn, error? } no (log-only)

Implementing a backend

Subclass CompactService, implement compactIfNeeded and compactRegion, and load the subclass as a plugin — it registers as ctx.compact. See @deepseek-ai/dsh-compact-basic for the reference implementation.