7.9 KiB
v0.15.0 Phase 2 — Changeset Notes
Files included in this zip (new)
server/compaction/estimator.go NEW — EstimateTokens, EstimatePath, EstimatePathFromSummary
server/compaction/estimator_test.go NEW — unit tests (no DB required)
server/compaction/scanner.go NEW — Scanner, scan loop, candidate query, shouldCompact
Surgical edits needed in existing files
1. server/main.go
Phase 1 already introduced compactionSvc. Now add the scanner below it
(after the compactionSvc line, before route registration):
import "git.gobha.me/xcaliber/chat-switchboard/compaction" // already present from Phase 1
// ── Auto-Compaction Scanner ───────────
compactionScanner := compaction.NewScanner(compactionSvc, stores, compaction.ScannerConfig{
Interval: 5 * time.Minute,
Concurrency: 2,
})
compactionScanner.Start()
defer compactionScanner.Stop() // drain in-flight compactions on shutdown
Place defer compactionScanner.Stop() near the existing
defer kbIngester.Wait() line.
Note: The scanner re-reads all config from global_settings each
tick. The ScannerConfig struct only sets the ticker interval and
semaphore size at startup. Everything else (enabled, threshold, cooldown)
is dynamic.
2. src/index.html
Add an auto-compaction section to the admin Settings tab. Insert before
the Encryption section (before <section class="admin-section"> with
🔐 Encryption):
<section class="settings-section">
<h3>Auto-Compaction</h3>
<label class="checkbox-label"><input type="checkbox" id="adminCompactionEnabled"> Enable automatic conversation compaction</label>
<p class="section-hint">When enabled, long conversations are automatically summarized in the background using the utility model. Ships off by default — requires a utility model role to be configured.</p>
<div id="compactionConfigFields" style="display:none;margin-top:8px">
<div class="form-row">
<div class="form-group">
<label>Threshold</label>
<div class="form-row" style="gap:4px;align-items:center">
<input type="number" id="adminCompactionThreshold" value="70" min="10" max="95" step="5" style="width:70px">
<span class="form-hint">% of context window</span>
</div>
</div>
<div class="form-group">
<label>Cooldown</label>
<div class="form-row" style="gap:4px;align-items:center">
<input type="number" id="adminCompactionCooldown" value="30" min="5" max="360" step="5" style="width:70px">
<span class="form-hint">minutes per channel</span>
</div>
</div>
</div>
</div>
</section>
3. src/js/ui-admin.js — loadAdminSettings()
Add these lines after the Web Search config loading block
(after document.getElementById('searxngConfigFields').style.display = ...):
// Auto-Compaction (global_settings)
const compactionCfg = getSetting('auto_compaction', {}) || {};
const compactionEnabled = document.getElementById('adminCompactionEnabled');
if (compactionEnabled) {
compactionEnabled.checked = !!compactionCfg.enabled;
document.getElementById('compactionConfigFields').style.display =
compactionCfg.enabled ? '' : 'none';
}
const compThreshold = document.getElementById('adminCompactionThreshold');
if (compThreshold) compThreshold.value = String(compactionCfg.threshold || 70);
const compCooldown = document.getElementById('adminCompactionCooldown');
if (compCooldown) compCooldown.value = String(compactionCfg.cooldown || 30);
4. src/js/settings-handlers.js — handleSaveAdminSettings()
Add these lines after the search_config save block (before
UI.toast('Settings saved', 'success')):
// Auto-Compaction config → global_settings
const compactionEnabled = document.getElementById('adminCompactionEnabled')?.checked || false;
const compactionThreshold = parseInt(document.getElementById('adminCompactionThreshold')?.value) || 70;
const compactionCooldown = parseInt(document.getElementById('adminCompactionCooldown')?.value) || 30;
await API.adminUpdateSetting('auto_compaction', { value: {
enabled: compactionEnabled,
threshold: compactionThreshold,
cooldown: compactionCooldown,
}});
// Persist individual keys for scanner (reads these independently)
await API.adminUpdateSetting('auto_compaction_enabled', { value: compactionEnabled });
await API.adminUpdateSetting('auto_compaction_threshold', { value: compactionThreshold / 100 });
await API.adminUpdateSetting('auto_compaction_cooldown_minutes', { value: compactionCooldown });
5. src/js/settings-handlers.js — _initSettingsListeners()
Add a toggle listener for the compaction checkbox, alongside the existing
banner toggle. Add after the adminBannerEnabled change listener:
document.getElementById('adminCompactionEnabled')?.addEventListener('change', (e) => {
document.getElementById('compactionConfigFields').style.display = e.target.checked ? '' : 'none';
});
6. src/js/ui-core.js — _summaryHTML()
Show "(auto)" label on auto-triggered summaries. The trigger field is
already in message metadata from Phase 1.
Replace the _summaryHTML method:
_summaryHTML(msg) {
const meta = msg.metadata || {};
const count = meta.summarized_count || '?';
const model = meta.utility_model || 'utility model';
const trigger = meta.trigger === 'auto' ? ' · auto' : '';
return `
<div class="message-summary" data-msg-id="${esc(msg.id || '')}">
<div class="summary-header">
<span>📝 Conversation summary (${count} messages, by ${esc(model)}${trigger})</span>
</div>
<div class="msg-text">${formatMessage(msg.content)}</div>
</div>`;
},
How the scanner reads settings
The scanner calls GlobalConfig.Get() every tick for these keys:
| Key | Read by | How stored |
|---|---|---|
auto_compaction_enabled |
isEnabled() |
{value: true/false} |
auto_compaction_threshold |
getThreshold() |
{value: 0.70} (float 0-1) |
auto_compaction_cooldown_minutes |
getCooldownDuration() |
{value: 30} |
The admin UI stores a combined auto_compaction key (for display) AND
the individual keys (for scanner consumption). This avoids the scanner
needing to parse a composite object.
Interval and concurrency are set at startup via ScannerConfig and
require a restart to change. This is intentional — they affect the
goroutine pool size and ticker, which shouldn't be hot-swapped.
Verification
cd server && go test ./compaction/— estimator tests pass (no DB)cd server && go build .— compiles cleanly- Manual test: Admin → Settings → enable auto-compaction → Save →
check server logs for
🔍 compaction scanner started - Manual test: with utility role configured and a long conversation, verify auto-compaction fires after the threshold is exceeded
- Verify cooldown: same channel should not re-compact within 30 minutes
- Verify kill switch: disable auto-compaction → scanner stops processing on next tick