Skip to content

Compression Strategies

New here? Start with the STM Overview to see the full pipeline in context first.

Every MCP tool response routed through STM passes through its compression gate before it reaches your agent. Direct upstream calls bypass STM. When a routed response exceeds its configured budget, STM compresses it — and the method depends on the content type.

memtomem-stm automatically compresses MCP tool responses by content type to save tokens. It ships 10 strategies in total — 8 content-type reducers plus the auto selector and the none passthrough — reducing response size while preserving the information the agent needs. If you’re not sure which to pick, leave the setting on auto — it chooses the right reducer per response from the immediate-response strategies.

StrategyTarget contentBehavior
truncateSmall textLength-limited truncation (default fallback)
hybridMarkdownPreserve structure + abbreviate non-essential sections
selectiveLarge structured dataTwo-phase TOC first, then retrieve selected sections on demand
progressiveLarge contentCursor-based sequential delivery of the retained source
extract_fieldsJSON dictionariesPreserve top-level shape with representative nested values
schema_pruningJSON arraysRecursive schema-preserving sampling
skeletonAPI docsPreserve headings and first content lines
llm_summaryComplex textLLM-based summarization (OpenAI/Anthropic/Ollama) — timeout-bound (default 60s)
autoAll typesAnalyze content and auto-select optimal strategy
none—Pass through original without compression

The auto strategy (default) analyzes content to pick the optimal strategy:

Content typeSelected strategy
Already within budgetnone
Large JSON array, or dict containing large arraysschema_pruning
Nested JSON dictionaryextract_fields
API docs with HTTP endpointsskeleton
Large structured Markdown / code-heavy texthybrid
Other text or simple JSONtruncate

selective, progressive, and llm_summary are opt-in only. auto never chooses them because they change the interaction pattern or add external latency.

During compression, the agent’s current query is taken into account — relevant sections receive a larger token budget. The selective / hybrid / schema_pruning / skeleton strategies also rank table-of-contents entries by BM25 relevance to the active query. The deterministic BM25 score used here is recorded by selection telemetry for offline analysis (see Configuration).

Budgets can be expressed as max_result_chars or max_result_tokens. Token budgets use chars_per_token plus token_estimation_mode (static or Unicode-aware unicode), while consumer_model and context_budget_ratio can cap a result against the receiving model’s context window. Per-upstream and per-tool values override proxy defaults.

JSON-aware tiers re-serialize strict JSON after compression. Non-finite values such as NaN, Infinity, and -Infinity are mapped to null before JSON output so downstream parsers do not receive Python extension tokens. The JSON tiers degrade monotonically as budgets shrink. The documented exception is standalone selective: it shrinks per-entry previews first, but a zero-preview TOC envelope can still exceed budget at very high section counts because dropping entries would break the selection contract.

The progressive strategy delivers retained content sequentially, subject to the recovery boundaries below:

  1. First response delivers a table of contents (TOC) and the first chunk
  2. Agent calls stm_proxy_read_more(key, offset) → cursor-based delivery of subsequent chunks
  3. Full content can be inspected sequentially

Every progressive chunk ends with the canonical footer \n---\n[progressive: chars=<n>] — agents must split on the full PROGRESSIVE_FOOTER_TOKEN string (exported from memtomem_stm.proxy.progressive). Splitting on \n---\n alone silently drops bytes when content contains Markdown horizontal rules or YAML fences.

Per-response follow-up rate and coverage for progressive delivery — along with degradation to passthrough when the primary store fails — are reported by stm_admin(action="progressive_stats") (see MCP Tools).

The response cache uses schema 5 and preserves the canonical MCP envelope, including content, structuredContent, and _meta. An incompatible older cache is reset once instead of mixing envelope versions. An upstream result that quotes STM’s progressive footer (\n---\n[progressive: chars=) is cached unless it also carries the stm_proxy_read_more(key="…") call.

The retention floor (MEMTOMEM_STM_PROXY__MIN_RESULT_RETENTION, default 0.65) guards against over-compression. When an output drops below the floor, a 3-tier fallback activates automatically:

progressive → hybrid → truncate

Each tier checks the floor — if satisfied, that strategy’s output is used. The char budget is raised to len(response) * min_result_retention before truncation when per-tool max_result_chars would otherwise drop more than the floor allows.

  • Rounding the minimum: the minimum is rounded up to a whole character and passed to plain-text truncation. Truncation picks a sentence or word boundary only at or above it, and otherwise cuts at the budget. This covers the truncate strategy, llm_summary without an LLM configured, the schema-pruning and skeleton plain-text fallbacks, store-error degradation and the terminal truncate tier.
    • A plain-text cut that used to fall a few characters short of the floor, and switch to progressive delivery, now arrives as one truncated result of at least the floor.
    • A result that still falls short, such as a structural cut, takes its fallback as before.
  • Explicit retention_floor: a tool retention_floor overrides a server one, which overrides the global ladder; null inherits and an explicit 0 opts that level out. An explicit retention_floor applies even with min_result_retention: 0. A tool set to 1.0, for example, is preserved in full rather than cut to its character budget.

The llm_summary strategy has its own timeout guard: the llm_timeout_seconds field (default 60s) on the per-server / per-tool llm block. A slow or stuck LLM endpoint no longer blocks the proxy — on timeout, STM falls back to truncate so the agent still receives a bounded response. An empty or whitespace-only summary makes the compressor fall back to the truncated original, recorded as llm_summary→llm_empty_fallback when that result clears the retention floor; if it does not, the retention guard replaces it as for any other result (for example llm_summary→progressive_fallback). Because the endpoint answered, an empty summary does not count as a circuit-breaker failure. Anthropic responses are read from every text block, not only the first.

Agent feedback automatically adjusts per-tool compression budgets:

  • Agent reports information loss → Increase preservation ratio for that tool
  • Agent reports response too long → Decrease preservation ratio

This feedback loop is driven by the stm_compression_feedback tool; accumulated feedback and per-tool adjustments are visible via stm_admin(action="compression_stats") (see MCP Tools).

Progressive delivery retains the cleaned source after upstream limits, not an unlimited byte-for-byte copy of the original response. Pending selections are memory-backed by default; SQLite persistence is opt-in. Entries expire under their TTL. An expired ID returns an error, not an automatic upstream refetch. Repeating an original read-only call is a separate, explicit action; do not replay a write just to recover content.

Cache schema 5 uses length-prefixed key framing and Unicode serialization. Older cache rows are discarded on initialization; this is separate from deleting LTM memories.