Files
waggle-os/packages/hive-mind-core/tests/integration/full-stack.test.ts
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

389 lines
16 KiB
TypeScript

import { describe, it, expect, afterEach } from 'vitest';
import { MindDB } from '@waggle/hive-mind-core';
import { IdentityLayer } from '@waggle/hive-mind-core';
import { AwarenessLayer } from '@waggle/hive-mind-core';
import { FrameStore } from '@waggle/hive-mind-core';
import { SessionStore } from '@waggle/hive-mind-core';
import { HybridSearch } from '@waggle/hive-mind-core';
import { KnowledgeGraph } from '@waggle/hive-mind-core';
import { MemoryWeaver } from '@waggle/weaver';
import { Orchestrator } from '@waggle/agent';
import { MockEmbedder } from '../mind/helpers/mock-embedder.js';
import {
PROGRAM_REGISTRY,
createSummarizer,
createClassifier,
createPromptExpander,
} from '@waggle/optimizer';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
describe('Full Integration Test', () => {
let db: MindDB;
let tmpFile: string | null = null;
afterEach(() => {
db?.close();
if (tmpFile && fs.existsSync(tmpFile)) {
fs.unlinkSync(tmpFile);
// Clean up WAL/SHM files
for (const suffix of ['-wal', '-shm']) {
const f = tmpFile + suffix;
if (fs.existsSync(f)) fs.unlinkSync(f);
}
}
});
describe('Complete lifecycle', () => {
it('runs full agent lifecycle: identity → awareness → frames → knowledge → consolidation → reload', async () => {
db = new MindDB(':memory:');
const embedder = new MockEmbedder();
// --- Step 1: Set identity ---
const identity = new IdentityLayer(db);
identity.create({
name: 'Waggle',
role: 'Personal AI Assistant',
department: 'Engineering',
personality: 'Helpful, precise, proactive',
capabilities: 'Memory, search, knowledge graph, task management',
system_prompt: 'You are Waggle, a personal AI assistant.',
});
expect(identity.get().name).toBe('Waggle');
// --- Step 2: Populate awareness ---
const awareness = new AwarenessLayer(db);
awareness.add('task', 'Review quarterly report', 8);
awareness.add('task', 'Prepare team standup notes', 5);
awareness.add('flag', 'User prefers concise responses', 10);
awareness.add('pending', 'Waiting for API key from DevOps', 3);
expect(awareness.getAll().length).toBe(4);
// --- Step 3: Create sessions and memory frames ---
const sessions = new SessionStore(db);
const frames = new FrameStore(db);
// Session 1: Morning work
const session1 = sessions.create('project-alpha');
const iframe1 = frames.createIFrame(session1.gop_id, 'Started working on Project Alpha. Main goal: refactor authentication module.', 'important');
frames.createPFrame(session1.gop_id, 'Identified 3 deprecated auth methods that need replacement.', iframe1.id);
frames.createPFrame(session1.gop_id, 'Created migration plan for auth refactoring.', iframe1.id);
frames.createPFrame(session1.gop_id, 'Reviewed PR #42 - found potential security issue in token validation.', iframe1.id);
// Session 2: Afternoon research
const session2 = sessions.create('project-alpha');
const iframe2 = frames.createIFrame(session2.gop_id, 'Researching OAuth 2.0 best practices for the auth refactor.');
frames.createPFrame(session2.gop_id, 'PKCE flow is recommended for public clients. Added to implementation plan.', iframe2.id);
frames.createPFrame(session2.gop_id, 'Found library @auth/core that handles most OAuth flows.', iframe2.id);
// Cross-reference between sessions
frames.createBFrame(session2.gop_id, 'Links research to auth refactor plan', iframe2.id, [iframe1.id]);
// Verify GOP structure
const gop1Frames = frames.getGopFrames(session1.gop_id);
expect(gop1Frames.length).toBe(4); // 1 I + 3 P
const gop2Frames = frames.getGopFrames(session2.gop_id);
expect(gop2Frames.length).toBe(4); // 1 I + 2 P + 1 B
// State reconstruction
const state1 = frames.reconstructState(session1.gop_id);
expect(state1.iframe).toBeTruthy();
expect(state1.pframes.length).toBe(3);
// --- Step 4: Build knowledge graph ---
const knowledge = new KnowledgeGraph(db);
const projectAlpha = knowledge.createEntity('project', 'Project Alpha', { status: 'active', priority: 'high' });
const authModule = knowledge.createEntity('module', 'Authentication Module', { language: 'TypeScript' });
const oauth = knowledge.createEntity('technology', 'OAuth 2.0', { version: '2.1' });
const alice = knowledge.createEntity('person', 'Alice', { role: 'Tech Lead' });
knowledge.createRelation(projectAlpha.id, authModule.id, 'contains', 1.0);
knowledge.createRelation(authModule.id, oauth.id, 'uses', 0.9);
knowledge.createRelation(alice.id, projectAlpha.id, 'leads', 0.95);
// Graph traversal
const reachable = knowledge.traverse(projectAlpha.id, 'contains', 2);
expect(reachable.length).toBeGreaterThanOrEqual(1);
expect(reachable.some(e => e.name === 'Authentication Module')).toBe(true);
// --- Step 5: Run consolidation ---
const weaver = new MemoryWeaver(db, frames, sessions);
// Consolidate session 1
const consolidated = weaver.consolidateGop(session1.gop_id);
expect(consolidated).toBeTruthy();
expect(consolidated!.content).toContain('refactor authentication');
expect(consolidated!.content).toContain('deprecated auth methods');
// Close and archive old session
sessions.close(session1.gop_id, 'Completed auth refactor planning');
const archived = weaver.archiveClosedSessions();
expect(archived).toBe(1);
// Project consolidation
const projectSummary = weaver.consolidateProject('project-alpha');
expect(projectSummary).toBeTruthy();
// --- Step 6: Simulate new session reload ---
// This simulates what happens when the agent "wakes up"
const identity2 = new IdentityLayer(db);
expect(identity2.get().name).toBe('Waggle');
const awareness2 = new AwarenessLayer(db);
expect(awareness2.getAll().length).toBe(4);
// Active sessions still available
const active = sessions.getActive();
expect(active.length).toBeGreaterThanOrEqual(1);
// Memory is searchable
const search = new HybridSearch(db, embedder);
// Keyword search works directly via FTS (indexed during frame creation)
const keywordResults = await search.keywordSearch('authentication', 20);
expect(keywordResults.length).toBeGreaterThan(0);
});
});
describe('Wakeup performance', () => {
it('total wakeup time under 300ms (identity + awareness + memory reconstruction)', () => {
db = new MindDB(':memory:');
// Setup: create identity, awareness, and some frames
const identity = new IdentityLayer(db);
identity.create({
name: 'Waggle',
role: 'Assistant',
department: '',
personality: '',
capabilities: '',
system_prompt: '',
});
const awareness = new AwarenessLayer(db);
for (let i = 0; i < 10; i++) {
awareness.add('task', `Task ${i}`, i);
}
const sessions = new SessionStore(db);
const frames = new FrameStore(db);
const session = sessions.create();
const iframe = frames.createIFrame(session.gop_id, 'Base state');
for (let i = 0; i < 50; i++) {
frames.createPFrame(session.gop_id, `Memory item ${i}`, iframe.id);
}
// Benchmark wakeup
const iterations = 100;
const start = performance.now();
for (let i = 0; i < iterations; i++) {
identity.get();
awareness.getAll();
frames.reconstructState(session.gop_id);
}
const elapsed = performance.now() - start;
const avgMs = elapsed / iterations;
expect(avgMs).toBeLessThan(300);
// In practice should be well under 10ms
console.log(` Wakeup avg: ${avgMs.toFixed(2)}ms over ${iterations} iterations`);
});
});
describe('Scale test: 10,000+ memories', () => {
it('10,000 memories searchable without performance degradation', async () => {
db = new MindDB(':memory:');
const embedder = new MockEmbedder();
const sessions = new SessionStore(db);
const frames = new FrameStore(db);
const search = new HybridSearch(db, embedder);
const session = sessions.create();
const iframe = frames.createIFrame(session.gop_id, 'Initial state for scale test');
// Insert 10,000 P-frames in batches
const TOTAL = 10_000;
const BATCH = 1000;
const raw = db.getDatabase();
const insertStart = performance.now();
const insertFrame = raw.prepare(`
INSERT INTO memory_frames (frame_type, gop_id, t, base_frame_id, content, importance)
VALUES ('P', ?, ?, ?, ?, 'normal')
`);
const insertFts = raw.prepare(`
INSERT INTO memory_frames_fts (rowid, content) VALUES (?, ?)
`);
const insertBatch = raw.transaction((startIdx: number, count: number) => {
for (let i = startIdx; i < startIdx + count; i++) {
const content = `Memory entry ${i}: This is a detailed observation about topic-${i % 100} with context about area-${i % 50}.`;
const result = insertFrame.run(session.gop_id, i + 1, iframe.id, content);
insertFts.run(result.lastInsertRowid, content);
}
});
for (let batch = 0; batch < TOTAL / BATCH; batch++) {
insertBatch(batch * BATCH, BATCH);
}
const insertElapsed = performance.now() - insertStart;
console.log(` Insert 10K frames: ${insertElapsed.toFixed(0)}ms`);
expect(insertElapsed).toBeLessThan(10_000); // Under 10s
// Index a subset for vector search (indexing all 10K is slow with mock)
const sampled = [];
for (let i = 0; i < 100; i++) {
const frameId = i * 100 + 2; // Skip the I-frame at id=1
sampled.push({ id: frameId, content: `Memory entry ${i * 100}: topic-${(i * 100) % 100}` });
}
await search.indexFramesBatch(sampled);
// Keyword search performance (quote terms to avoid FTS5 operator interpretation)
const searchStart = performance.now();
const keywordResults = await search.keywordSearch('"topic-42"', 20);
const searchElapsed = performance.now() - searchStart;
console.log(` Keyword search (10K): ${searchElapsed.toFixed(2)}ms, found ${keywordResults.length} results`);
expect(keywordResults.length).toBeGreaterThan(0);
expect(searchElapsed).toBeLessThan(200);
// Hybrid search performance
const hybridStart = performance.now();
const hybridResults = await search.search('"topic-42" "area-25"', { limit: 10 });
const hybridElapsed = performance.now() - hybridStart;
console.log(` Hybrid search (10K): ${hybridElapsed.toFixed(2)}ms, found ${hybridResults.length} results`);
expect(hybridResults.length).toBeGreaterThan(0);
expect(hybridElapsed).toBeLessThan(500);
// State reconstruction performance — cap is loose because this test
// can be run alongside heavy parallel suites where CPU contention
// pushes timings well past the theoretical ~10ms best case.
const reconStart = performance.now();
const state = frames.reconstructState(session.gop_id);
const reconElapsed = performance.now() - reconStart;
console.log(` State reconstruction (10K): ${reconElapsed.toFixed(2)}ms`);
expect(state.iframe).toBeTruthy();
expect(reconElapsed).toBeLessThan(500);
});
});
describe('.mind file portability', () => {
it('.mind file is a single portable file under 500MB for reasonable data', () => {
// Create a file-backed .mind database
tmpFile = path.join(os.tmpdir(), `waggle-test-${Date.now()}.mind`);
db = new MindDB(tmpFile);
const identity = new IdentityLayer(db);
identity.create({
name: 'Test Agent',
role: 'Tester',
department: '',
personality: '',
capabilities: '',
system_prompt: '',
});
const sessions = new SessionStore(db);
const frames = new FrameStore(db);
// Create 100 sessions with 100 frames each = 10,000 frames
const raw = db.getDatabase();
const insertFrame = raw.prepare(`
INSERT INTO memory_frames (frame_type, gop_id, t, base_frame_id, content, importance)
VALUES ('P', ?, ?, NULL, ?, 'normal')
`);
const insertFts = raw.prepare(`
INSERT INTO memory_frames_fts (rowid, content) VALUES (?, ?)
`);
const insertAll = raw.transaction(() => {
for (let s = 0; s < 100; s++) {
const session = sessions.create(`project-${s % 10}`);
const iframe = frames.createIFrame(session.gop_id, `Session ${s} base state with detailed context about the work being done.`);
for (let f = 0; f < 99; f++) {
const content = `Session ${s}, frame ${f}: Detailed observation about ${['engineering', 'design', 'research', 'planning'][f % 4]} work.`;
const result = insertFrame.run(session.gop_id, f + 1, content);
insertFts.run(result.lastInsertRowid, content);
}
}
});
insertAll();
// Check file size
db.close();
const stats = fs.statSync(tmpFile);
const sizeMB = stats.size / (1024 * 1024);
console.log(` .mind file size (10K frames): ${sizeMB.toFixed(2)} MB`);
expect(sizeMB).toBeLessThan(500);
// Verify it's a single file (WAL checkpoint)
expect(fs.existsSync(tmpFile)).toBe(true);
// Reopen and verify data is intact
db = new MindDB(tmpFile);
const identity2 = new IdentityLayer(db);
expect(identity2.get().name).toBe('Test Agent');
const sessions2 = new SessionStore(db);
const allSessions = raw.prepare ? undefined : undefined;
// Just verify we can query
const activeSessions = sessions2.getActive();
// All should be active (we never closed them)
expect(activeSessions.length).toBe(100);
});
});
describe('Orchestrator integration', () => {
it('orchestrator ties all components together', async () => {
db = new MindDB(':memory:');
const embedder = new MockEmbedder();
const orchestrator = new Orchestrator({ db, embedder });
// Set identity via orchestrator
orchestrator.getIdentity().create({
name: 'Waggle',
role: 'Integration Test Agent',
department: '',
personality: 'Thorough',
capabilities: 'Full stack',
system_prompt: 'You are running integration tests.',
});
// Add awareness via tool
await orchestrator.executeTool('add_task', { content: 'Run all integration tests', priority: 10 });
// Save memories via tool
await orchestrator.executeTool('save_memory', { content: 'Integration test started successfully', importance: 'normal' });
await orchestrator.executeTool('save_memory', { content: 'All components initialized', importance: 'important' });
// CognifyPipeline auto-indexes frames for vector search
// Search via tool
const searchResult = await orchestrator.executeTool('search_memory', { query: 'Integration' });
expect(searchResult).toContain('Integration');
// Knowledge graph via tool
orchestrator.getKnowledge().createEntity('test', 'IntegrationSuite', { status: 'running' });
const kgResult = await orchestrator.executeTool('query_knowledge', { query: 'IntegrationSuite' });
expect(kgResult).toContain('IntegrationSuite');
// System prompt includes everything
const prompt = orchestrator.buildSystemPrompt();
expect(prompt).toContain('Waggle');
expect(prompt).toContain('Run all integration tests');
expect(prompt).toContain('search_memory');
});
});
describe('Optimizer integration', () => {
it('all Ax programs are properly defined and constructable', () => {
expect(PROGRAM_REGISTRY.length).toBe(3);
for (const entry of PROGRAM_REGISTRY) {
const program = entry.create();
expect(program).toBeDefined();
expect(entry.signature.getInputFields().length).toBeGreaterThan(0);
expect(entry.signature.getOutputFields().length).toBeGreaterThan(0);
}
});
});
});