187 lines
7.9 KiB
Markdown
187 lines
7.9 KiB
Markdown
# 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):
|
|
|
|
```go
|
|
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`):
|
|
|
|
```html
|
|
<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 = ...`):
|
|
|
|
```javascript
|
|
// 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')`):
|
|
|
|
```javascript
|
|
// 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:
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
_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
|
|
|
|
1. `cd server && go test ./compaction/` — estimator tests pass (no DB)
|
|
2. `cd server && go build .` — compiles cleanly
|
|
3. Manual test: Admin → Settings → enable auto-compaction → Save →
|
|
check server logs for `🔍 compaction scanner started`
|
|
4. Manual test: with utility role configured and a long conversation,
|
|
verify auto-compaction fires after the threshold is exceeded
|
|
5. Verify cooldown: same channel should not re-compact within 30 minutes
|
|
6. Verify kill switch: disable auto-compaction → scanner stops processing
|
|
on next tick
|