feat: add multi-model delegation (Phase 0) and context compaction (Phase 1)

Phase 0 — Multi-Model Delegation:
- AgentOrchestrator wraps NativeAgent with delegate() for stateless
  single-turn calls to any model tier (fast/default/complex/local)
- DelegationConfig maps task types (compaction, classification, etc.)
  to model tiers
- Delegation prompts for compaction, memory extraction, classification,
  and tool summarisation
- Per-tier usage tracking for cost visibility
- Config schema: agents.delegation and agents.primary_tier

Phase 1 — Context Compaction:
- Token estimation (char/4 heuristic) with context window lookup
- shouldCompact() threshold check against context window percentage
- compactHistory() splits old/recent messages, delegates summary to
  fast tier, returns CompactionResult
- Automatic compaction in AgentOrchestrator.process() when configured
- Force-compact via orchestrator.compact() with session persistence
- Session.replaceHistory() with atomic SQLite transaction
- /compact TUI command with feedback on compacted token counts
- Config schema: compaction.enabled, threshold_pct, keep_turns,
  summary_max_tokens

Tests: 385 passing across 50 files (22 new tests in 2 new test files)
This commit is contained in:
William Valentin
2026-02-06 13:17:02 -08:00
parent f7cc87a4bb
commit 306e11bd2e
22 changed files with 1562 additions and 12 deletions
+20
View File
@@ -40,6 +40,26 @@ export class SessionStore {
}));
}
/**
* Atomically replace all messages for a session.
* Used by compaction to swap full history with a compacted version.
* Runs in a transaction: delete all → re-insert in order.
*/
replaceMessages(sessionId: string, messages: Message[]): void {
const transaction = this.db.transaction(() => {
// Delete existing messages
this.db.prepare('DELETE FROM messages WHERE session_id = ?').run(sessionId);
// Re-insert in order
const insert = this.db.prepare(
'INSERT INTO messages (session_id, role, content) VALUES (?, ?, ?)'
);
for (const msg of messages) {
insert.run(sessionId, msg.role, msg.content);
}
});
transaction();
}
clearSession(sessionId: string): void {
const stmt = this.db.prepare('DELETE FROM messages WHERE session_id = ?');
stmt.run(sessionId);