@tinyclaw/compactor
Enhanced conversation compaction system. Automatically compresses long conversation histories using a 4-layer pipeline: rule-based pre-compression, message deduplication, LLM summarization, and tiered summaries.
Installation
npm install @tinyclaw/compactor
Core Concepts
Compaction reduces conversation history size while preserving important context:
4-Layer Pipeline
-
Layer 1: Rule-based pre-compression (9 rules)
- Deduplicate lines
- Remove emoji
- Collapse whitespace
- Normalize CJK punctuation
- Remove empty sections
- Compress markdown tables
- Merge similar bullets
- Merge short bullets
- Remove decorative lines
-
Layer 2: Message deduplication
- Shingle hashing + Jaccard similarity
- Removes duplicate/highly similar messages
-
Layer 3: LLM summarization
- Single LLM call to generate L2 (detailed) summary
- Preserves key facts, decisions, and outcomes
-
Layer 4: Tiered summaries
- L0: Ultra-brief (1-2 sentences)
- L1: Brief (1 paragraph)
- L2: Detailed (multiple paragraphs)
Standalone Utilities
- Dictionary encoding: Auto-learned codebook, $XX substitution (lossless)
- Tokenizer optimizer: Encoding-aware format fixes (1-3% savings)
- Compressed Context Protocol (CCP): Ultra/medium/light abbreviation (20-60%)
Main Exports
Compactor Engine
createCompactor(db: Database, config?: CompactorConfig): CompactorEngine
Create a compactor engine.
Database instance from @tinyclaw/core
import { createCompactor } from '@tinyclaw/compactor';
import { createDatabase } from '@tinyclaw/core';
const db = createDatabase('/path/to/tiny-claw.db');
const compactor = createCompactor(db, {
threshold: 50, // Compact when message count > 50
targetCount: 20, // Keep last 20 messages after compaction
estimatedTokenLimit: 4000, // Token budget for compaction
});
Default Config:
const DEFAULT_COMPACTOR_CONFIG: CompactorConfig = {
threshold: 50,
targetCount: 20,
estimatedTokenLimit: 4000,
};
Methods
compactIfNeeded(userId, provider): Promise<CompactionResult | null>
Automatically compact conversation history if it exceeds threshold.
LLM provider for summarization
Compaction result or null if compaction was not needed
const result = await compactor.compactIfNeeded('web:owner', provider);
if (result) {
console.log(`Compacted ${result.messagesRemoved} messages`);
console.log(`Summary: ${result.summary.l1}`);
}
CompactionResult:
interface CompactionResult {
summary: TieredSummary; // L0, L1, L2 summaries
messagesRemoved: number; // Messages deleted
originalCount: number; // Messages before compaction
newCount: number; // Messages after compaction
metrics: CompactionMetrics; // Compression stats
}
TieredSummary:
interface TieredSummary {
l0: string; // Ultra-brief (1-2 sentences)
l1: string; // Brief (1 paragraph)
l2: string; // Detailed (multiple paragraphs)
}
getLatestSummary(userId): string | null
Retrieve the latest compaction summary.
const summary = compactor.getLatestSummary('web:owner');
if (summary) {
console.log(summary);
}
estimateTokens(text): number
Estimate token count for a text string.
const tokens = compactor.estimateTokens('Hello, world!');
console.log(`Estimated tokens: ${tokens}`);
Standalone Utilities
Compressed Context Protocol (CCP)
Ultra-aggressive abbreviation for context compression.
compressContext(text, level?): string
Compression level: 'ultra', 'medium', or 'light' (default: 'medium')
import { compressContext } from '@tinyclaw/compactor';
const original = 'The user asked about timezone configuration and we updated FRIEND.md with UTC+08:00.';
const compressed = compressContext(original, 'ultra');
console.log(compressed);
// Output: "usr askd tz cfg, upd FRIEND.md w/ UTC+08"
Compression Levels:
- ultra: 40-60% reduction (aggressive abbreviation)
- medium: 20-40% reduction (balanced)
- light: 10-20% reduction (conservative)
compressContextWithStats(text, level?): CcpResultWithStats
Compress and return statistics.
import { compressContextWithStats } from '@tinyclaw/compactor';
const result = compressContextWithStats(original, 'ultra');
console.log(result.compressed);
console.log(`Saved: ${result.stats.compressionRatio.toFixed(2)}%`);
Dictionary Encoding
Lossless compression using auto-learned codebook.
buildCodebook(text, options?): Codebook
Build a codebook from sample text.
import { buildCodebook } from '@tinyclaw/compactor';
const codebook = buildCodebook(conversationHistory, {
minFrequency: 3,
maxEntries: 100,
});
console.log(codebook);
// { 'timezone': '$01', 'configuration': '$02', ... }
compressText(text, codebook): string
Compress text using a codebook.
import { compressText } from '@tinyclaw/compactor';
const compressed = compressText(
'The timezone configuration is in FRIEND.md',
codebook
);
console.log(compressed);
// "The $01 $02 is in FRIEND.md"
decompressText(compressed, codebook): string
Decompress text.
import { decompressText } from '@tinyclaw/compactor';
const decompressed = decompressText(compressed, codebook);
console.log(decompressed);
// "The timezone configuration is in FRIEND.md"
compressionStats(original, compressed): { ratio, saved }
Calculate compression statistics.
import { compressionStats } from '@tinyclaw/compactor';
const stats = compressionStats(original, compressed);
console.log(`Compression ratio: ${stats.ratio.toFixed(2)}%`);
console.log(`Bytes saved: ${stats.saved}`);
Tokenizer Optimizer
Encoding-aware format fixes for better tokenization.
optimizeTokens(text, options?): string
Optimize text for tokenization.
import { optimizeTokens } from '@tinyclaw/compactor';
const optimized = optimizeTokens(text, {
stripBold: true,
stripItalic: true,
compactBullets: true,
minimizeWhitespace: true,
});
Individual Optimizers:
stripBoldItalic(text) - Remove bold/italic markdown
stripTrivialBackticks(text) - Remove unnecessary code fences
compactBullets(text) - Shorten bullet points
minimizeWhitespace(text) - Collapse whitespace
compressTableToKv(text) - Convert markdown tables to key-value
Rule-based Pre-compression
import {
preCompress,
deduplicateLines,
stripEmoji,
collapseWhitespace,
normalizeCjkPunctuation,
removeEmptySections,
compressMarkdownTable,
mergeSimilarBullets,
mergeShortBullets,
removeDecorativeLines,
} from '@tinyclaw/compactor';
// Apply all 9 rules at once
const compressed = preCompress(text);
// Or apply rules individually
const step1 = deduplicateLines(text);
const step2 = stripEmoji(step1);
const step3 = collapseWhitespace(step2);
Message Deduplication
import { deduplicateMessages, computeShingles, jaccardSimilarity } from '@tinyclaw/compactor';
const messages = [
{ role: 'user', content: 'Hello there!' },
{ role: 'user', content: 'Hello there!' }, // Duplicate
{ role: 'assistant', content: 'Hi!' },
];
const deduplicated = deduplicateMessages(messages, 0.8);
console.log(deduplicated.length); // 2
Tiered Summaries
import { generateTiers } from '@tinyclaw/compactor';
const l2Summary = 'The user asked about timezone configuration...';
const tiers = generateTiers(l2Summary);
console.log(`L0: ${tiers.l0}`);
console.log(`L1: ${tiers.l1}`);
console.log(`L2: ${tiers.l2}`);
Token Utilities
import { estimateTokens, truncateToTokenBudget } from '@tinyclaw/compactor';
const tokens = estimateTokens('Hello, world!');
console.log(`Estimated tokens: ${tokens}`);
const truncated = truncateToTokenBudget(text, 100);
console.log(`Truncated to 100 tokens: ${truncated}`);
Example Usage
Automatic Compaction
import { createDatabase } from '@tinyclaw/core';
import { createCompactor } from '@tinyclaw/compactor';
const db = createDatabase('/path/to/tiny-claw.db');
const compactor = createCompactor(db);
// In agent loop (before processing message)
const result = await compactor.compactIfNeeded('web:owner', provider);
if (result) {
console.log(`Compacted ${result.messagesRemoved} messages`);
console.log(`Saved ~${result.metrics.compressionRatio.toFixed(2)}% tokens`);
}
Manual Compaction
import { createCompactor } from '@tinyclaw/compactor';
const compactor = createCompactor(db, {
threshold: 100,
targetCount: 30,
});
// Force compaction
const result = await compactor.compactIfNeeded('web:owner', provider);
CCP for Ultra Compression
import { compressContext } from '@tinyclaw/compactor';
const longContext = `
The user asked about timezone configuration. We discussed UTC+08:00 for Philippines.
Updated FRIEND.md with location and timezone information.
User confirmed the changes and thanked me.
`;
const compressed = compressContext(longContext, 'ultra');
console.log(compressed);
// "usr askd tz cfg. disc UTC+08 PH. upd FRIEND.md w/ loc&tz. usr conf&thx"
Dictionary Encoding Workflow
import { buildCodebook, compressText, decompressText } from '@tinyclaw/compactor';
// Build codebook from conversation history
const history = db.getHistory('web:owner', 100);
const historyText = history.map(m => m.content).join('\n');
const codebook = buildCodebook(historyText);
// Compress new message
const message = 'The timezone configuration is stored in FRIEND.md';
const compressed = compressText(message, codebook);
// Decompress when needed
const decompressed = decompressText(compressed, codebook);
- Layer 1 (rules): ~5ms for 10KB text
- Layer 2 (dedup): ~10ms for 100 messages
- Layer 3 (LLM): ~2-5s (depends on provider)
- Layer 4 (tiers): ~1ms (heuristic-based)
- CCP: ~2ms for 10KB text
- Dictionary: ~5ms encode/decode for 10KB text
Compression Ratios:
- Layer 1: 10-20% reduction
- Layer 2: 5-15% reduction
- Layer 3: 60-80% reduction
- Layer 4: 0% (no additional compression, just formatting)
- CCP ultra: 40-60% reduction
- Dictionary: 10-30% reduction (depends on repetition)