feat: add heartbeat monitor and vector memory search (Tier 2)

Heartbeat:
- HeartbeatMonitor with 5 checks: gateway, model, channels, memory, disk
- Configurable interval, failure threshold, notification channel
- Recovery notifications when health restores
- 25 new tests

Vector Memory Search:
- EmbeddingProvider interface with OpenAI, Gemini, Ollama, LlamaCpp backends
- SQLite-backed VectorStore with cosine similarity search
- Text chunker with paragraph-aware splitting and overlap
- HybridSearch merging keyword + vector results with configurable weight
- Background indexer with dirty-namespace tracking
- Graceful fallback to keyword search when embeddings unavailable
- 51 new tests

Config: automation.heartbeat + memory.embedding schema sections
Total: 950 tests passing, all types clean
This commit is contained in:
William Valentin
2026-02-07 14:45:11 -08:00
parent b50c140d25
commit 88731a50e3
17 changed files with 2354 additions and 7 deletions
+35
View File
@@ -119,9 +119,24 @@ const webhookSchema = z.object({
enabled: z.boolean().default(true),
});
const heartbeatCheckSchema = z.enum(['gateway', 'model', 'channels', 'memory', 'disk']);
const heartbeatSchema = z.object({
enabled: z.boolean().default(false),
interval: z.string().default('5m'),
checks: z.array(heartbeatCheckSchema).default(['gateway', 'model', 'channels', 'memory', 'disk']),
notify: z.object({
channel: z.string().min(1),
peer: z.string().min(1),
}).optional(),
failure_threshold: z.number().min(1).max(10).default(2),
disk_threshold_mb: z.number().min(10).default(100),
}).default({});
const automationSchema = z.object({
cron: z.array(cronJobSchema).default([]),
webhooks: z.array(webhookSchema).default([]),
heartbeat: heartbeatSchema,
}).default({});
const agentsSchema = z.object({
@@ -143,11 +158,27 @@ const agentsSchema = z.object({
max_delegation_depth: z.number().min(1).max(10).default(3),
}).default({});
const embeddingProviderSchema = z.enum(['openai', 'gemini', 'ollama', 'llamacpp']);
const embeddingSchema = z.object({
enabled: z.boolean().default(false),
provider: embeddingProviderSchema.default('openai'),
model: z.string().default('text-embedding-3-small'),
endpoint: z.string().optional(),
api_key: z.string().optional(),
dimensions: z.number().optional(),
chunk_size: z.number().min(64).max(8192).default(512),
chunk_overlap: z.number().min(0).max(1024).default(50),
top_k: z.number().min(1).max(50).default(5),
hybrid_weight: z.number().min(0).max(1).default(0.7),
}).default({});
const memorySchema = z.object({
enabled: z.boolean().default(true),
dir: z.string().optional(), // Default: ~/.local/share/flynn/memory
auto_extract: z.boolean().default(true),
max_context_tokens: z.number().min(100).max(10000).default(2000),
embedding: embeddingSchema,
}).default({});
const compactionSchema = z.object({
@@ -333,3 +364,7 @@ export type RoutingConfig = z.infer<typeof routingSchema>;
export type ServerConfig = z.infer<typeof serverSchema>;
export type SessionsConfig = z.infer<typeof sessionsSchema>;
export type ThinkingConfig = z.infer<typeof thinkingSchema>;
export type HeartbeatConfig = z.infer<typeof heartbeatSchema>;
export type HeartbeatCheck = z.infer<typeof heartbeatCheckSchema>;
export type EmbeddingConfig = z.infer<typeof embeddingSchema>;
export type EmbeddingProvider = z.infer<typeof embeddingProviderSchema>;