titan-agent 5.5.7 → 5.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/memory/learning.js
CHANGED
|
@@ -99,6 +99,16 @@ function classifyErrorPattern(error) {
|
|
|
99
99
|
if (importLines >= 3) {
|
|
100
100
|
return { pattern: "build-dumped-source:import-block", isFileDump: true };
|
|
101
101
|
}
|
|
102
|
+
if (/▲\s+Next\.js\s+[\d.]+|Creating an optimized production build|Linting and checking validity/.test(trimmed)) {
|
|
103
|
+
return { pattern: "build-noise:nextjs-output", isFileDump: true };
|
|
104
|
+
}
|
|
105
|
+
const vitestMatch = trimmed.match(/^expected\s+.+\s+to\s+(be|deeply equal|have|contain|match)\b/);
|
|
106
|
+
if (vitestMatch) {
|
|
107
|
+
return { pattern: `test-noise:vitest-assertion:${vitestMatch[1]}`, isFileDump: true };
|
|
108
|
+
}
|
|
109
|
+
if (trimmed.startsWith("build-dumped-source:")) {
|
|
110
|
+
return { pattern: trimmed.split("\n")[0].slice(0, 200), isFileDump: true };
|
|
111
|
+
}
|
|
102
112
|
if (trimmed.length > 1200) {
|
|
103
113
|
return { pattern: "oversized-error:" + trimmed.slice(0, 60).replace(/\s+/g, " "), isFileDump: true };
|
|
104
114
|
}
|
|
@@ -224,8 +234,13 @@ function verifyMemoryStaleness() {
|
|
|
224
234
|
return age < SIXTY_DAYS || e.score > 0.7;
|
|
225
235
|
});
|
|
226
236
|
pruned = before - k.entries.length;
|
|
237
|
+
const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1e3;
|
|
227
238
|
for (const [pattern, info] of Object.entries(k.errorPatterns)) {
|
|
228
|
-
|
|
239
|
+
const ageMs = now - new Date(info.lastSeen).getTime();
|
|
240
|
+
if (ageMs > THIRTY_DAYS) {
|
|
241
|
+
delete k.errorPatterns[pattern];
|
|
242
|
+
pruned++;
|
|
243
|
+
} else if (ageMs > SEVEN_DAYS && !info.resolution) {
|
|
229
244
|
delete k.errorPatterns[pattern];
|
|
230
245
|
pruned++;
|
|
231
246
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/memory/learning.ts"],"sourcesContent":["/**\n * TITAN — Learning Engine\n * Continuous self-improvement: learns from interactions, tracks patterns,\n * builds a knowledge base, and improves tool selection over time.\n */\nimport { existsSync, readFileSync, writeFileSync, renameSync } from 'fs';\nimport { join } from 'path';\nimport { TITAN_HOME } from '../utils/constants.js';\nimport { mkdirIfNotExists } from '../utils/helpers.js';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'Learning';\nconst KNOWLEDGE_FILE = join(TITAN_HOME, 'knowledge.json');\n\nexport interface LearningEntry {\n id: string;\n type: 'skill_usage' | 'error_pattern' | 'user_correction' | 'successful_pattern' | 'fact' | 'preference';\n category: string;\n content: string;\n context?: string;\n score: number; // confidence/usefulness score (0-1)\n accessCount: number; // how often this knowledge was retrieved\n createdAt: string;\n updatedAt: string;\n}\n\n/** Tool preference by task type (Phase 4 — Active Learning) */\ninterface ToolPreference {\n tool: string;\n successRate: number;\n totalUses: number;\n}\n\n/** Strategy memory — records what approaches worked for task patterns */\ninterface StrategyEntry {\n pattern: string; // Task pattern description\n toolsUsed: string[]; // Deduplicated set\n toolSequence?: string[]; // Ordered sequence of tool calls (preserves order + repeats)\n taskType?: string; // Classified task type (coding, research, etc.)\n roundCount: number;\n success: boolean;\n successCount?: number; // How many times this sequence has succeeded\n failCount?: number; // How many times this strategy failed after being applied\n lastValidated?: string; // ISO timestamp of last successful validation\n timestamp: string;\n}\n\ninterface KnowledgeBase {\n entries: LearningEntry[];\n toolSuccessRates: Record<string, { success: number; fail: number; total: number }>;\n /** Tool success rates segmented by task type (coding, research, analysis, etc.) */\n toolPreferencesByType: Record<string, Record<string, { success: number; total: number }>>;\n /** Strategy memory — top 50 strategies, evicted by age + success */\n strategies: StrategyEntry[];\n errorPatterns: Record<string, { count: number; lastSeen: string; resolution?: string }>;\n userCorrections: Array<{ original: string; correction: string; timestamp: string }>;\n conversationInsights: Array<{ topic: string; outcome: string; toolsUsed: string[]; timestamp: string }>;\n}\n\nlet kb: KnowledgeBase | null = null;\nlet dirty = false;\n\n// ── Strategy hint caches ──────────────────────────────────────────\nlet successfulStrategiesCache: StrategyEntry[] | null = null;\nlet lastDecayRun = 0;\nconst DECAY_INTERVAL_MS = 3600_000; // 1 hour\nconst patternWordsCache = new Map<string, Set<string>>();\n\nfunction invalidateStrategyCaches(): void {\n successfulStrategiesCache = null;\n}\n\nfunction getPatternWords(pattern: string): Set<string> {\n let words = patternWordsCache.get(pattern);\n if (!words) {\n words = new Set(pattern.toLowerCase().split(/\\s+/).filter(w => w.length > 3));\n patternWordsCache.set(pattern, words);\n }\n return words;\n}\n\n// NOTE: Sync I/O is intentional — runs only once at cold start, then cached in-memory.\nfunction loadKnowledgeBase(): KnowledgeBase {\n if (kb) return kb;\n mkdirIfNotExists(TITAN_HOME);\n if (existsSync(KNOWLEDGE_FILE)) {\n try {\n kb = JSON.parse(readFileSync(KNOWLEDGE_FILE, 'utf-8'));\n // Ensure fields exist\n kb!.entries = kb!.entries || [];\n kb!.toolSuccessRates = kb!.toolSuccessRates || {};\n kb!.toolPreferencesByType = kb!.toolPreferencesByType || {};\n kb!.strategies = kb!.strategies || [];\n kb!.errorPatterns = kb!.errorPatterns || {};\n kb!.userCorrections = kb!.userCorrections || [];\n kb!.conversationInsights = kb!.conversationInsights || [];\n } catch {\n kb = createEmptyKB();\n }\n } else {\n kb = createEmptyKB();\n }\n return kb!;\n}\n\nfunction createEmptyKB(): KnowledgeBase {\n return {\n entries: [],\n toolSuccessRates: {},\n toolPreferencesByType: {},\n strategies: [],\n errorPatterns: {},\n userCorrections: [],\n conversationInsights: [],\n };\n}\n\nfunction doSave(): void {\n if (!kb) return;\n mkdirIfNotExists(TITAN_HOME);\n try {\n const tmpFile = KNOWLEDGE_FILE + '.tmp';\n writeFileSync(tmpFile, JSON.stringify(kb, null, 2), 'utf-8');\n renameSync(tmpFile, KNOWLEDGE_FILE);\n dirty = false;\n } catch (err) {\n dirty = true;\n logger.error(COMPONENT, `Failed to save knowledge base: ${(err as Error).message}`);\n }\n}\n\nlet saveTimeout: ReturnType<typeof setTimeout> | null = null;\nfunction debouncedSave(): void {\n if (dirty) { doSave(); return; }\n if (saveTimeout) clearTimeout(saveTimeout);\n saveTimeout = setTimeout(doSave, 2000);\n saveTimeout.unref();\n}\n\n/** Initialize the learning engine */\nexport function initLearning(): void {\n loadKnowledgeBase();\n logger.info(COMPONENT, `Learning engine initialized (${kb?.entries.length ?? 0} knowledge entries)`);\n}\n\n/**\n * v4.9.0-local.9: detect \"error\" strings that are actually file content\n * echoed back as context (common when build tools dump failing source).\n * The pre-.9 behavior was to slice(0, 200) and dedupe on that prefix,\n * which made the same file show up many times and inflated Curiosity's\n * \"unresolved patterns\" count by ~5-10x. Observed on 2026-04-18: 327\n * total patterns, of which >50% were just slices of titan-saas source.\n *\n * Heuristic: if the string looks like source code being printed back,\n * don't record it as a distinct error pattern. Specifically:\n * - starts with \"File: /path/to/...\" (compiler output convention)\n * - contains \"--- 1:\" or \"--- 2:\" etc. (line-numbered code dumps)\n * - is >1200 chars (real error strings are usually short; anything\n * longer is almost always dumped source)\n * - is an entire TypeScript/JS import block (≥3 `import ` lines)\n *\n * When detected, we record a GENERIC rollup pattern (\"build-dumped-source:\n * <basename>\") instead of the raw content, so Curiosity sees one entry per\n * file instead of N copies of the same file's content.\n */\nfunction classifyErrorPattern(error: string): { pattern: string; isFileDump: boolean } {\n const trimmed = error.trim();\n const basename = (m: RegExpMatchArray | null): string => {\n const path = m?.[1] ?? '';\n return path.split('/').pop() ?? 'unknown';\n };\n\n // \"File: /path/to/foo.ts (N lines) --- ...\"\n const fileHeaderMatch = trimmed.match(/^File:\\s+(\\S+)\\s+\\(\\d+\\s+lines\\)/);\n if (fileHeaderMatch) {\n return { pattern: `build-dumped-source:${basename(fileHeaderMatch)}`, isFileDump: true };\n }\n\n // Multi-line numbered code dump (\"--- 1: ... --- 2: ...\")\n if (/---\\s*\\d+:/.test(trimmed) && /---\\s*2:/.test(trimmed)) {\n return { pattern: 'build-dumped-source:numbered-code-block', isFileDump: true };\n }\n\n // Import blocks (TypeScript/JS) — ≥3 import lines → probably source\n const importLines = (trimmed.match(/^import\\s+/gm) || []).length;\n if (importLines >= 3) {\n return { pattern: 'build-dumped-source:import-block', isFileDump: true };\n }\n\n // Too long to be a useful signature\n if (trimmed.length > 1200) {\n return { pattern: 'oversized-error:' + trimmed.slice(0, 60).replace(/\\s+/g, ' '), isFileDump: true };\n }\n\n // Normal short error — keep the original 200-char slice behavior\n return { pattern: trimmed.slice(0, 200), isFileDump: false };\n}\n\n/** Record a tool execution result for learning */\nexport function recordToolResult(toolName: string, success: boolean, context?: string, error?: string): void {\n const k = loadKnowledgeBase();\n\n // Update tool success rates\n if (!k.toolSuccessRates[toolName]) {\n k.toolSuccessRates[toolName] = { success: 0, fail: 0, total: 0 };\n }\n k.toolSuccessRates[toolName].total++;\n if (success) {\n k.toolSuccessRates[toolName].success++;\n } else {\n k.toolSuccessRates[toolName].fail++;\n // Track error patterns — with file-content detection (v4.9.0-local.9)\n if (error) {\n const { pattern } = classifyErrorPattern(error);\n if (!k.errorPatterns[pattern]) {\n k.errorPatterns[pattern] = { count: 0, lastSeen: '' };\n }\n k.errorPatterns[pattern].count++;\n k.errorPatterns[pattern].lastSeen = new Date().toISOString();\n }\n }\n\n debouncedSave();\n}\n\n// Exposed for tests + one-time migration tooling\nexport { classifyErrorPattern };\n\n/** Record a successful interaction pattern */\nexport function recordSuccessPattern(pattern: {\n topic: string;\n toolsUsed: string[];\n outcome: string;\n}): void {\n const k = loadKnowledgeBase();\n k.conversationInsights.push({\n ...pattern,\n timestamp: new Date().toISOString(),\n });\n // Keep last 500 insights\n if (k.conversationInsights.length > 500) {\n k.conversationInsights = k.conversationInsights.slice(-500);\n }\n debouncedSave();\n}\n\n/** Record a user correction to learn from mistakes */\nexport function recordUserCorrection(original: string, correction: string): void {\n const k = loadKnowledgeBase();\n k.userCorrections.push({\n original,\n correction,\n timestamp: new Date().toISOString(),\n });\n if (k.userCorrections.length > 200) {\n k.userCorrections = k.userCorrections.slice(-200);\n }\n debouncedSave();\n}\n\n/** Learn a new fact or update an existing one */\nexport function learnFact(category: string, content: string, context?: string): void {\n const k = loadKnowledgeBase();\n const id = `${category}:${content.slice(0, 50)}`;\n const existing = k.entries.findIndex((e) => e.id === id);\n\n if (existing >= 0) {\n k.entries[existing].score = Math.min(k.entries[existing].score + 0.1, 1.0);\n k.entries[existing].accessCount++;\n k.entries[existing].updatedAt = new Date().toISOString();\n } else {\n k.entries.push({\n id,\n type: 'fact',\n category,\n content,\n context,\n score: 0.5,\n accessCount: 0,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n });\n }\n // Keep entries under 2000\n if (k.entries.length > 2000) {\n // Remove lowest-scored entries\n k.entries.sort((a, b) => b.score - a.score);\n k.entries = k.entries.slice(0, 1500);\n }\n debouncedSave();\n}\n\n/** Query the knowledge base for relevant information */\nexport function queryKnowledge(query: string, category?: string, limit: number = 10): LearningEntry[] {\n const k = loadKnowledgeBase();\n const q = query.toLowerCase();\n\n const results = k.entries.filter((e) => {\n const matchesQuery = e.content.toLowerCase().includes(q) ||\n e.category.toLowerCase().includes(q) ||\n (e.context && e.context.toLowerCase().includes(q));\n const matchesCategory = !category || e.category === category;\n return matchesQuery && matchesCategory;\n });\n\n // Sort by score (most useful first), then by recency\n results.sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score;\n return b.updatedAt.localeCompare(a.updatedAt);\n });\n\n // Boost access count for retrieved entries\n for (const entry of results.slice(0, limit)) {\n entry.accessCount++;\n entry.score = Math.min(entry.score + 0.01, 1.0);\n }\n\n debouncedSave();\n return results.slice(0, limit);\n}\n\n/** Get tool recommendations based on historical success rates */\nexport function getToolRecommendations(): Record<string, number> {\n const k = loadKnowledgeBase();\n const recommendations: Record<string, number> = {};\n\n for (const [tool, stats] of Object.entries(k.toolSuccessRates)) {\n if (stats.total > 0) {\n recommendations[tool] = stats.success / stats.total;\n }\n }\n\n return recommendations;\n}\n\n/**\n * TITAN pattern: Memory staleness verification\n * Before acting on learned knowledge, verify it's still current.\n * Strategies unvalidated for 30+ days lose 20% successCount.\n * Knowledge entries older than 60 days get flagged as potentially stale.\n */\nexport function verifyMemoryStaleness(): { pruned: number; decayed: number } {\n const k = loadKnowledgeBase();\n const now = Date.now();\n const THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000;\n const SIXTY_DAYS = 60 * 24 * 60 * 60 * 1000;\n let pruned = 0;\n let decayed = 0;\n\n // Decay strategies unvalidated for 30+ days\n for (const strategy of k.strategies) {\n const lastValidated = strategy.lastValidated ? new Date(strategy.lastValidated).getTime() : new Date(strategy.timestamp).getTime();\n if (now - lastValidated > THIRTY_DAYS && (strategy.successCount || 0) > 0) {\n strategy.successCount = Math.floor((strategy.successCount || 0) * 0.8);\n decayed++;\n }\n }\n\n // Remove knowledge entries older than 60 days with low scores\n const before = k.entries.length;\n k.entries = k.entries.filter(e => {\n const age = now - new Date(e.createdAt).getTime();\n return age < SIXTY_DAYS || e.score > 0.7;\n });\n pruned = before - k.entries.length;\n\n // Remove error patterns not seen in 30+ days\n for (const [pattern, info] of Object.entries(k.errorPatterns)) {\n if (now - new Date(info.lastSeen).getTime() > THIRTY_DAYS) {\n delete k.errorPatterns[pattern];\n pruned++;\n }\n }\n\n if (pruned > 0 || decayed > 0) {\n doSave();\n logger.info(COMPONENT, `Memory staleness check: ${pruned} pruned, ${decayed} strategies decayed`);\n }\n\n return { pruned, decayed };\n}\n\n/**\n * v4.9.0-local.9: one-time cleanup of \"error patterns\" that are actually\n * file content dumps. Retroactively applies the same classifyErrorPattern\n * logic that now runs on record. Collapses dozens of sliced file-content\n * entries into a single rollup per file.\n *\n * Returns { removed, collapsedInto } where `removed` is the number of raw\n * entries deleted and `collapsedInto` is how many rollup entries were\n * produced (usually much smaller).\n */\nexport function pruneFileContentErrorPatterns(): { removed: number; collapsedInto: number } {\n const k = loadKnowledgeBase();\n const rollup = new Map<string, { count: number; lastSeen: string; resolution?: string }>();\n const keep: Record<string, { count: number; lastSeen: string; resolution?: string }> = {};\n let removed = 0;\n\n for (const [pattern, info] of Object.entries(k.errorPatterns)) {\n const { pattern: canonical, isFileDump } = classifyErrorPattern(pattern);\n if (isFileDump) {\n // Collapse into the rollup bucket\n const existing = rollup.get(canonical);\n if (existing) {\n existing.count += info.count;\n if (info.lastSeen > existing.lastSeen) existing.lastSeen = info.lastSeen;\n if (!existing.resolution && info.resolution) existing.resolution = info.resolution;\n } else {\n rollup.set(canonical, { count: info.count, lastSeen: info.lastSeen, resolution: info.resolution });\n }\n removed++;\n } else {\n keep[pattern] = info;\n }\n }\n // Merge rollups back in (they may collide with real entries — count-sum if so)\n for (const [canonical, info] of rollup) {\n if (keep[canonical]) {\n keep[canonical].count += info.count;\n if (info.lastSeen > keep[canonical].lastSeen) keep[canonical].lastSeen = info.lastSeen;\n } else {\n keep[canonical] = info;\n }\n }\n k.errorPatterns = keep;\n doSave();\n logger.info(COMPONENT, `[PrunePatterns] removed ${removed} file-content entries, collapsed into ${rollup.size} rollup entries`);\n return { removed, collapsedInto: rollup.size };\n}\n\n/** Get learning summary for the system prompt */\nexport function getLearningContext(): string {\n const k = loadKnowledgeBase();\n const parts: string[] = [];\n\n // Recent high-score knowledge\n const topEntries = k.entries\n .filter((e) => e.score > 0.6)\n .sort((a, b) => b.score - a.score)\n .slice(0, 10);\n\n if (topEntries.length > 0) {\n parts.push('Key learned facts:');\n for (const e of topEntries) {\n parts.push(`- [${e.category}] ${e.content}`);\n }\n }\n\n // Tool recommendations\n const toolRecs = getToolRecommendations();\n const bestTools = Object.entries(toolRecs)\n .filter(([_, rate]) => rate > 0.8)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 5);\n\n if (bestTools.length > 0) {\n parts.push('\\nMost reliable tools:');\n for (const [tool, rate] of bestTools) {\n parts.push(`- ${tool}: ${Math.round(rate * 100)}% success rate`);\n }\n }\n\n // Common error patterns to avoid\n const frequentErrors = Object.entries(k.errorPatterns)\n .filter(([_, info]) => info.count > 2)\n .sort((a, b) => b[1].count - a[1].count)\n .slice(0, 3);\n\n if (frequentErrors.length > 0) {\n parts.push('\\nCommon errors to avoid:');\n for (const [pattern, info] of frequentErrors) {\n parts.push(`- ${pattern.slice(0, 100)} (seen ${info.count}x)${info.resolution ? ` → Fix: ${info.resolution}` : ''}`);\n }\n }\n\n return parts.join('\\n');\n}\n\n/** Get per-tool reliability warnings/boosts for tool description injection */\nexport function getToolWarnings(): Record<string, string> {\n const k = loadKnowledgeBase();\n const warnings: Record<string, string> = {};\n\n for (const [tool, stats] of Object.entries(k.toolSuccessRates)) {\n if (stats.total < 10) continue; // Need enough data to be meaningful\n const rate = stats.success / stats.total;\n if (rate < 0.3) {\n warnings[tool] = `[LOW RELIABILITY: ${Math.round(rate * 100)}% success rate over ${stats.total} uses]`;\n } else if (rate > 0.9) {\n warnings[tool] = `[HIGHLY RELIABLE: ${Math.round(rate * 100)}% success rate]`;\n }\n }\n\n return warnings;\n}\n\n/** Record when a tool failure is resolved by using a different tool */\nexport function recordErrorResolution(errorPattern: string, resolution: string): void {\n const k = loadKnowledgeBase();\n const pattern = errorPattern.slice(0, 200);\n if (k.errorPatterns[pattern]) {\n k.errorPatterns[pattern].resolution = resolution;\n } else {\n k.errorPatterns[pattern] = { count: 1, lastSeen: new Date().toISOString(), resolution };\n }\n debouncedSave();\n}\n\n/** Get stats about the learning system */\nexport function getLearningStats(): {\n knowledgeEntries: number;\n toolsTracked: number;\n errorPatterns: number;\n /**\n * v4.10.0-local fix: true count of UNRESOLVED patterns, used by the\n * curiosity drive. Prior behavior was to report `errorPatterns` (total)\n * as if it were unresolved — meaning marking patterns as resolved did\n * nothing to the drive signal. Now separate field.\n */\n unresolvedErrorPatterns: number;\n corrections: number;\n insights: number;\n strategies: number;\n taskTypes: number;\n} {\n const k = loadKnowledgeBase();\n const unresolvedErrorPatterns = Object.values(k.errorPatterns).filter(v => !v.resolution).length;\n return {\n knowledgeEntries: k.entries.length,\n toolsTracked: Object.keys(k.toolSuccessRates).length,\n errorPatterns: Object.keys(k.errorPatterns).length,\n unresolvedErrorPatterns,\n corrections: k.userCorrections.length,\n insights: k.conversationInsights.length,\n strategies: k.strategies.length,\n taskTypes: Object.keys(k.toolPreferencesByType).length,\n };\n}\n\n// ── Phase 4: Active Learning ──────────────────────────────────────\n\n/** Classify a message into a task type for preference tracking */\nexport function classifyTaskType(message: string): string {\n const lower = message.toLowerCase();\n if (/\\b(code|function|class|typescript|python|script|debug|compile|build)\\b/.test(lower)) return 'coding';\n if (/\\b(search|research|find|look up|investigate|compare)\\b/.test(lower)) return 'research';\n if (/\\b(analy[sz]e|data|csv|chart|graph|statistics|metrics)\\b/.test(lower)) return 'analysis';\n if (/\\b(write|draft|blog|article|email|message|story)\\b/.test(lower)) return 'writing';\n if (/\\b(deploy|server|docker|kubernetes|ci|cd|infrastructure)\\b/.test(lower)) return 'devops';\n if (/\\b(file|folder|directory|rename|move|copy|delete)\\b/.test(lower)) return 'filesystem';\n if (/\\b(schedule|cron|automat|workflow|remind)\\b/.test(lower)) return 'automation';\n return 'general';\n}\n\n/** Record a tool result against a specific task type */\nexport function recordToolPreference(toolName: string, taskType: string, success: boolean): void {\n const k = loadKnowledgeBase();\n if (!k.toolPreferencesByType[taskType]) {\n k.toolPreferencesByType[taskType] = {};\n }\n if (!k.toolPreferencesByType[taskType][toolName]) {\n k.toolPreferencesByType[taskType][toolName] = { success: 0, total: 0 };\n }\n\n k.toolPreferencesByType[taskType][toolName].total++;\n if (success) k.toolPreferencesByType[taskType][toolName].success++;\n debouncedSave();\n}\n\n/** Get ranked tool preferences for a task type */\nexport function getToolPreferences(taskType: string): ToolPreference[] {\n const k = loadKnowledgeBase();\n const prefs = k.toolPreferencesByType[taskType];\n if (!prefs) return [];\n\n return Object.entries(prefs)\n .filter(([, stats]) => stats.total >= 3) // Minimum sample size\n .map(([tool, stats]) => ({\n tool,\n successRate: stats.success / stats.total,\n totalUses: stats.total,\n }))\n .sort((a, b) => b.successRate - a.successRate);\n}\n\n/** Generate human-readable preference hints from collected tool-preference data */\nexport function getLearnedPreferenceHints(taskType: string): string | null {\n const prefs = getToolPreferences(taskType);\n if (prefs.length < 2) return null;\n\n const lines: string[] = [];\n const FILE_TOOLS = ['read_file', 'write_file', 'edit_file', 'list_dir'];\n const shellPref = prefs.find(p => p.tool === 'shell');\n\n // Find cases where shell has lower success than dedicated tools\n for (const dt of prefs.filter(p => FILE_TOOLS.includes(p.tool))) {\n if (shellPref && dt.successRate > shellPref.successRate && dt.totalUses >= 3) {\n lines.push(\n `prefer ${dt.tool} (${Math.round(dt.successRate * 100)}% success) ` +\n `over shell (${Math.round(shellPref.successRate * 100)}% success)`,\n );\n }\n }\n\n // Surface top tools for this task type if no shell comparison available\n if (lines.length === 0) {\n const top3 = prefs.slice(0, 3);\n if (top3.length >= 2) {\n lines.push(`best tools: ${top3.map(p => `${p.tool} (${Math.round(p.successRate * 100)}%)`).join(', ')}`);\n }\n }\n\n return lines.length > 0 ? `For ${taskType} tasks: ${lines.join('; ')}` : null;\n}\n\n/** Record a successful strategy for future reference */\nexport function recordStrategy(\n message: string,\n toolsUsed: string[],\n roundCount: number,\n success: boolean,\n toolSequence?: string[],\n): void {\n const k = loadKnowledgeBase();\n const taskType = classifyTaskType(message);\n\n // Check if a similar sequence already exists — merge instead of duplicating\n if (success && toolSequence && toolSequence.length > 0) {\n const seqKey = toolSequence.join('→');\n const existing = k.strategies.find(\n s => s.success && s.taskType === taskType && s.toolSequence?.join('→') === seqKey,\n );\n if (existing) {\n existing.successCount = (existing.successCount || 1) + 1;\n existing.timestamp = new Date().toISOString();\n debouncedSave();\n return;\n }\n }\n\n const entry: StrategyEntry = {\n pattern: message.slice(0, 200),\n toolsUsed: [...new Set(toolsUsed)],\n toolSequence: toolSequence?.slice(0, 20), // Cap sequence length\n taskType,\n roundCount,\n success,\n successCount: success ? 1 : 0,\n timestamp: new Date().toISOString(),\n };\n\n k.strategies.push(entry);\n invalidateStrategyCaches();\n\n // Evict old/failed strategies to keep at 200 max\n if (k.strategies.length > 200) {\n // Sort: keep high-success + recent, evict failed + old + low-success\n k.strategies.sort((a, b) => {\n if (a.success !== b.success) return a.success ? -1 : 1;\n const aCount = a.successCount || 1;\n const bCount = b.successCount || 1;\n if (aCount !== bCount) return bCount - aCount;\n return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();\n });\n k.strategies = k.strategies.slice(0, 200);\n }\n\n debouncedSave();\n}\n\n/** Record outcome of an applied strategy (feedback loop) */\nexport function recordStrategyOutcome(\n taskType: string,\n toolSequence: string[],\n succeeded: boolean,\n): void {\n const k = loadKnowledgeBase();\n const seqKey = toolSequence.join('→');\n const match = k.strategies.find(\n s => s.taskType === taskType && s.toolSequence?.join('→') === seqKey,\n );\n if (!match) return;\n\n if (succeeded) {\n match.successCount = (match.successCount || 1) + 1;\n match.lastValidated = new Date().toISOString();\n } else {\n match.failCount = (match.failCount || 0) + 1;\n }\n\n // High-fail strategies get marked as unsuccessful\n const becameUnsuccessful = (match.failCount || 0) > (match.successCount || 1) && match.success;\n if (becameUnsuccessful) {\n match.success = false;\n }\n\n if (becameUnsuccessful) invalidateStrategyCaches();\n debouncedSave();\n}\n\n/** Decay unvalidated strategies — called on every getStrategyHints */\nfunction decayStrategies(strategies: StrategyEntry[]): boolean {\n const now = Date.now();\n let changed = false;\n\n for (const s of strategies) {\n const lastValidated = s.lastValidated\n ? new Date(s.lastValidated).getTime()\n : new Date(s.timestamp).getTime();\n const daysSinceValidated = (now - lastValidated) / 86400000;\n\n // Strategies not validated in 30 days: reduce successCount by 20%\n if (daysSinceValidated > 30 && (s.successCount || 1) > 1) {\n s.successCount = Math.max(1, Math.floor((s.successCount || 1) * 0.8));\n changed = true;\n }\n\n // High-fail strategies get excluded\n if ((s.failCount || 0) > (s.successCount || 1) && s.success) {\n s.success = false;\n changed = true;\n }\n }\n\n return changed;\n}\n\n/** Get strategy hints for a similar task */\nexport function getStrategyHints(message: string): string | null {\n const k = loadKnowledgeBase();\n\n // Apply decay to stale strategies\n const now = Date.now();\n if (now - lastDecayRun > DECAY_INTERVAL_MS) {\n lastDecayRun = now;\n if (decayStrategies(k.strategies)) {\n debouncedSave();\n invalidateStrategyCaches();\n }\n }\n if (k.strategies.length === 0) return null;\n\n const taskType = classifyTaskType(message);\n const words = new Set(message.toLowerCase().split(/\\s+/).filter(w => w.length > 3));\n if (words.size === 0) return null;\n\n const successfulStrategies = successfulStrategiesCache ?? (successfulStrategiesCache = k.strategies.filter(s => s.success));\n if (successfulStrategies.length === 0) return null;\n\n // Score strategies by: task type match + keyword overlap + success count\n let bestMatch: StrategyEntry | null = null;\n let bestScore = 0;\n\n for (const strategy of successfulStrategies) {\n const patternWords = getPatternWords(strategy.pattern);\n let overlap = 0;\n for (const w of words) {\n if (patternWords.has(w)) overlap++;\n }\n const keywordScore = overlap / Math.max(words.size, patternWords.size);\n\n // Task type match bonus (0.2)\n const typeBonus = strategy.taskType === taskType ? 0.2 : 0;\n\n // Success count bonus (normalized, max 0.15)\n const countBonus = Math.min((strategy.successCount || 1) / 10, 0.15);\n\n const totalScore = keywordScore + typeBonus + countBonus;\n if (totalScore > bestScore && keywordScore > 0.15) {\n bestScore = totalScore;\n bestMatch = strategy;\n }\n }\n\n if (!bestMatch) return null;\n\n // Prefer showing the ordered sequence if available\n const toolInfo = bestMatch.toolSequence && bestMatch.toolSequence.length > 0\n ? bestMatch.toolSequence.join(' → ')\n : bestMatch.toolsUsed.join(', ');\n\n const countInfo = (bestMatch.successCount || 1) > 1\n ? ` (succeeded ${bestMatch.successCount}x)`\n : '';\n\n return `For similar ${bestMatch.taskType || 'general'} tasks, a proven tool sequence: ${toolInfo} (${bestMatch.roundCount} rounds)${countInfo}.`;\n}\n\n/** Get error resolution if a known pattern matches */\nexport function getErrorResolution(error: string): string | null {\n const k = loadKnowledgeBase();\n const errorLower = error.toLowerCase();\n\n for (const [pattern, info] of Object.entries(k.errorPatterns)) {\n if (info.resolution && errorLower.includes(pattern.toLowerCase().slice(0, 50))) {\n return info.resolution;\n }\n }\n return null;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/memory/learning.ts"],"sourcesContent":["/**\n * TITAN — Learning Engine\n * Continuous self-improvement: learns from interactions, tracks patterns,\n * builds a knowledge base, and improves tool selection over time.\n */\nimport { existsSync, readFileSync, writeFileSync, renameSync } from 'fs';\nimport { join } from 'path';\nimport { TITAN_HOME } from '../utils/constants.js';\nimport { mkdirIfNotExists } from '../utils/helpers.js';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'Learning';\nconst KNOWLEDGE_FILE = join(TITAN_HOME, 'knowledge.json');\n\nexport interface LearningEntry {\n id: string;\n type: 'skill_usage' | 'error_pattern' | 'user_correction' | 'successful_pattern' | 'fact' | 'preference';\n category: string;\n content: string;\n context?: string;\n score: number; // confidence/usefulness score (0-1)\n accessCount: number; // how often this knowledge was retrieved\n createdAt: string;\n updatedAt: string;\n}\n\n/** Tool preference by task type (Phase 4 — Active Learning) */\ninterface ToolPreference {\n tool: string;\n successRate: number;\n totalUses: number;\n}\n\n/** Strategy memory — records what approaches worked for task patterns */\ninterface StrategyEntry {\n pattern: string; // Task pattern description\n toolsUsed: string[]; // Deduplicated set\n toolSequence?: string[]; // Ordered sequence of tool calls (preserves order + repeats)\n taskType?: string; // Classified task type (coding, research, etc.)\n roundCount: number;\n success: boolean;\n successCount?: number; // How many times this sequence has succeeded\n failCount?: number; // How many times this strategy failed after being applied\n lastValidated?: string; // ISO timestamp of last successful validation\n timestamp: string;\n}\n\ninterface KnowledgeBase {\n entries: LearningEntry[];\n toolSuccessRates: Record<string, { success: number; fail: number; total: number }>;\n /** Tool success rates segmented by task type (coding, research, analysis, etc.) */\n toolPreferencesByType: Record<string, Record<string, { success: number; total: number }>>;\n /** Strategy memory — top 50 strategies, evicted by age + success */\n strategies: StrategyEntry[];\n errorPatterns: Record<string, { count: number; lastSeen: string; resolution?: string }>;\n userCorrections: Array<{ original: string; correction: string; timestamp: string }>;\n conversationInsights: Array<{ topic: string; outcome: string; toolsUsed: string[]; timestamp: string }>;\n}\n\nlet kb: KnowledgeBase | null = null;\nlet dirty = false;\n\n// ── Strategy hint caches ──────────────────────────────────────────\nlet successfulStrategiesCache: StrategyEntry[] | null = null;\nlet lastDecayRun = 0;\nconst DECAY_INTERVAL_MS = 3600_000; // 1 hour\nconst patternWordsCache = new Map<string, Set<string>>();\n\nfunction invalidateStrategyCaches(): void {\n successfulStrategiesCache = null;\n}\n\nfunction getPatternWords(pattern: string): Set<string> {\n let words = patternWordsCache.get(pattern);\n if (!words) {\n words = new Set(pattern.toLowerCase().split(/\\s+/).filter(w => w.length > 3));\n patternWordsCache.set(pattern, words);\n }\n return words;\n}\n\n// NOTE: Sync I/O is intentional — runs only once at cold start, then cached in-memory.\nfunction loadKnowledgeBase(): KnowledgeBase {\n if (kb) return kb;\n mkdirIfNotExists(TITAN_HOME);\n if (existsSync(KNOWLEDGE_FILE)) {\n try {\n kb = JSON.parse(readFileSync(KNOWLEDGE_FILE, 'utf-8'));\n // Ensure fields exist\n kb!.entries = kb!.entries || [];\n kb!.toolSuccessRates = kb!.toolSuccessRates || {};\n kb!.toolPreferencesByType = kb!.toolPreferencesByType || {};\n kb!.strategies = kb!.strategies || [];\n kb!.errorPatterns = kb!.errorPatterns || {};\n kb!.userCorrections = kb!.userCorrections || [];\n kb!.conversationInsights = kb!.conversationInsights || [];\n } catch {\n kb = createEmptyKB();\n }\n } else {\n kb = createEmptyKB();\n }\n return kb!;\n}\n\nfunction createEmptyKB(): KnowledgeBase {\n return {\n entries: [],\n toolSuccessRates: {},\n toolPreferencesByType: {},\n strategies: [],\n errorPatterns: {},\n userCorrections: [],\n conversationInsights: [],\n };\n}\n\nfunction doSave(): void {\n if (!kb) return;\n mkdirIfNotExists(TITAN_HOME);\n try {\n const tmpFile = KNOWLEDGE_FILE + '.tmp';\n writeFileSync(tmpFile, JSON.stringify(kb, null, 2), 'utf-8');\n renameSync(tmpFile, KNOWLEDGE_FILE);\n dirty = false;\n } catch (err) {\n dirty = true;\n logger.error(COMPONENT, `Failed to save knowledge base: ${(err as Error).message}`);\n }\n}\n\nlet saveTimeout: ReturnType<typeof setTimeout> | null = null;\nfunction debouncedSave(): void {\n if (dirty) { doSave(); return; }\n if (saveTimeout) clearTimeout(saveTimeout);\n saveTimeout = setTimeout(doSave, 2000);\n saveTimeout.unref();\n}\n\n/** Initialize the learning engine */\nexport function initLearning(): void {\n loadKnowledgeBase();\n logger.info(COMPONENT, `Learning engine initialized (${kb?.entries.length ?? 0} knowledge entries)`);\n}\n\n/**\n * v4.9.0-local.9: detect \"error\" strings that are actually file content\n * echoed back as context (common when build tools dump failing source).\n * The pre-.9 behavior was to slice(0, 200) and dedupe on that prefix,\n * which made the same file show up many times and inflated Curiosity's\n * \"unresolved patterns\" count by ~5-10x. Observed on 2026-04-18: 327\n * total patterns, of which >50% were just slices of titan-saas source.\n *\n * Heuristic: if the string looks like source code being printed back,\n * don't record it as a distinct error pattern. Specifically:\n * - starts with \"File: /path/to/...\" (compiler output convention)\n * - contains \"--- 1:\" or \"--- 2:\" etc. (line-numbered code dumps)\n * - is >1200 chars (real error strings are usually short; anything\n * longer is almost always dumped source)\n * - is an entire TypeScript/JS import block (≥3 `import ` lines)\n *\n * When detected, we record a GENERIC rollup pattern (\"build-dumped-source:\n * <basename>\") instead of the raw content, so Curiosity sees one entry per\n * file instead of N copies of the same file's content.\n */\nfunction classifyErrorPattern(error: string): { pattern: string; isFileDump: boolean } {\n const trimmed = error.trim();\n const basename = (m: RegExpMatchArray | null): string => {\n const path = m?.[1] ?? '';\n return path.split('/').pop() ?? 'unknown';\n };\n\n // \"File: /path/to/foo.ts (N lines) --- ...\"\n const fileHeaderMatch = trimmed.match(/^File:\\s+(\\S+)\\s+\\(\\d+\\s+lines\\)/);\n if (fileHeaderMatch) {\n return { pattern: `build-dumped-source:${basename(fileHeaderMatch)}`, isFileDump: true };\n }\n\n // Multi-line numbered code dump (\"--- 1: ... --- 2: ...\")\n if (/---\\s*\\d+:/.test(trimmed) && /---\\s*2:/.test(trimmed)) {\n return { pattern: 'build-dumped-source:numbered-code-block', isFileDump: true };\n }\n\n // Import blocks (TypeScript/JS) — ≥3 import lines → probably source\n const importLines = (trimmed.match(/^import\\s+/gm) || []).length;\n if (importLines >= 3) {\n return { pattern: 'build-dumped-source:import-block', isFileDump: true };\n }\n\n // v5.5.8: Next.js build output — \"▲ Next.js X.Y.Z ... Compiled successfully ...\"\n // Was being recorded as 100s of \"error patterns\" because builds dump to stderr\n // even on success. Roll up to a single canonical entry.\n if (/▲\\s+Next\\.js\\s+[\\d.]+|Creating an optimized production build|Linting and checking validity/.test(trimmed)) {\n return { pattern: 'build-noise:nextjs-output', isFileDump: true };\n }\n\n // v5.5.8: Vitest assertion failures — \"expected X to be Y // Object.is equality\"\n // These are stale test failures from past runs, not recurring runtime errors.\n // Roll up by assertion shape rather than specific values.\n const vitestMatch = trimmed.match(/^expected\\s+.+\\s+to\\s+(be|deeply equal|have|contain|match)\\b/);\n if (vitestMatch) {\n return { pattern: `test-noise:vitest-assertion:${vitestMatch[1]}`, isFileDump: true };\n }\n\n // v5.5.8: bare \"build-dumped-source:...\" lines (already-classified entries\n // that got re-recorded as errors). Treat as same canonical pattern.\n if (trimmed.startsWith('build-dumped-source:')) {\n return { pattern: trimmed.split('\\n')[0].slice(0, 200), isFileDump: true };\n }\n\n // Too long to be a useful signature\n if (trimmed.length > 1200) {\n return { pattern: 'oversized-error:' + trimmed.slice(0, 60).replace(/\\s+/g, ' '), isFileDump: true };\n }\n\n // Normal short error — keep the original 200-char slice behavior\n return { pattern: trimmed.slice(0, 200), isFileDump: false };\n}\n\n/** Record a tool execution result for learning */\nexport function recordToolResult(toolName: string, success: boolean, context?: string, error?: string): void {\n const k = loadKnowledgeBase();\n\n // Update tool success rates\n if (!k.toolSuccessRates[toolName]) {\n k.toolSuccessRates[toolName] = { success: 0, fail: 0, total: 0 };\n }\n k.toolSuccessRates[toolName].total++;\n if (success) {\n k.toolSuccessRates[toolName].success++;\n } else {\n k.toolSuccessRates[toolName].fail++;\n // Track error patterns — with file-content detection (v4.9.0-local.9)\n if (error) {\n const { pattern } = classifyErrorPattern(error);\n if (!k.errorPatterns[pattern]) {\n k.errorPatterns[pattern] = { count: 0, lastSeen: '' };\n }\n k.errorPatterns[pattern].count++;\n k.errorPatterns[pattern].lastSeen = new Date().toISOString();\n }\n }\n\n debouncedSave();\n}\n\n// Exposed for tests + one-time migration tooling\nexport { classifyErrorPattern };\n\n/** Record a successful interaction pattern */\nexport function recordSuccessPattern(pattern: {\n topic: string;\n toolsUsed: string[];\n outcome: string;\n}): void {\n const k = loadKnowledgeBase();\n k.conversationInsights.push({\n ...pattern,\n timestamp: new Date().toISOString(),\n });\n // Keep last 500 insights\n if (k.conversationInsights.length > 500) {\n k.conversationInsights = k.conversationInsights.slice(-500);\n }\n debouncedSave();\n}\n\n/** Record a user correction to learn from mistakes */\nexport function recordUserCorrection(original: string, correction: string): void {\n const k = loadKnowledgeBase();\n k.userCorrections.push({\n original,\n correction,\n timestamp: new Date().toISOString(),\n });\n if (k.userCorrections.length > 200) {\n k.userCorrections = k.userCorrections.slice(-200);\n }\n debouncedSave();\n}\n\n/** Learn a new fact or update an existing one */\nexport function learnFact(category: string, content: string, context?: string): void {\n const k = loadKnowledgeBase();\n const id = `${category}:${content.slice(0, 50)}`;\n const existing = k.entries.findIndex((e) => e.id === id);\n\n if (existing >= 0) {\n k.entries[existing].score = Math.min(k.entries[existing].score + 0.1, 1.0);\n k.entries[existing].accessCount++;\n k.entries[existing].updatedAt = new Date().toISOString();\n } else {\n k.entries.push({\n id,\n type: 'fact',\n category,\n content,\n context,\n score: 0.5,\n accessCount: 0,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n });\n }\n // Keep entries under 2000\n if (k.entries.length > 2000) {\n // Remove lowest-scored entries\n k.entries.sort((a, b) => b.score - a.score);\n k.entries = k.entries.slice(0, 1500);\n }\n debouncedSave();\n}\n\n/** Query the knowledge base for relevant information */\nexport function queryKnowledge(query: string, category?: string, limit: number = 10): LearningEntry[] {\n const k = loadKnowledgeBase();\n const q = query.toLowerCase();\n\n const results = k.entries.filter((e) => {\n const matchesQuery = e.content.toLowerCase().includes(q) ||\n e.category.toLowerCase().includes(q) ||\n (e.context && e.context.toLowerCase().includes(q));\n const matchesCategory = !category || e.category === category;\n return matchesQuery && matchesCategory;\n });\n\n // Sort by score (most useful first), then by recency\n results.sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score;\n return b.updatedAt.localeCompare(a.updatedAt);\n });\n\n // Boost access count for retrieved entries\n for (const entry of results.slice(0, limit)) {\n entry.accessCount++;\n entry.score = Math.min(entry.score + 0.01, 1.0);\n }\n\n debouncedSave();\n return results.slice(0, limit);\n}\n\n/** Get tool recommendations based on historical success rates */\nexport function getToolRecommendations(): Record<string, number> {\n const k = loadKnowledgeBase();\n const recommendations: Record<string, number> = {};\n\n for (const [tool, stats] of Object.entries(k.toolSuccessRates)) {\n if (stats.total > 0) {\n recommendations[tool] = stats.success / stats.total;\n }\n }\n\n return recommendations;\n}\n\n/**\n * TITAN pattern: Memory staleness verification\n * Before acting on learned knowledge, verify it's still current.\n * Strategies unvalidated for 30+ days lose 20% successCount.\n * Knowledge entries older than 60 days get flagged as potentially stale.\n */\nexport function verifyMemoryStaleness(): { pruned: number; decayed: number } {\n const k = loadKnowledgeBase();\n const now = Date.now();\n const THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000;\n const SIXTY_DAYS = 60 * 24 * 60 * 60 * 1000;\n let pruned = 0;\n let decayed = 0;\n\n // Decay strategies unvalidated for 30+ days\n for (const strategy of k.strategies) {\n const lastValidated = strategy.lastValidated ? new Date(strategy.lastValidated).getTime() : new Date(strategy.timestamp).getTime();\n if (now - lastValidated > THIRTY_DAYS && (strategy.successCount || 0) > 0) {\n strategy.successCount = Math.floor((strategy.successCount || 0) * 0.8);\n decayed++;\n }\n }\n\n // Remove knowledge entries older than 60 days with low scores\n const before = k.entries.length;\n k.entries = k.entries.filter(e => {\n const age = now - new Date(e.createdAt).getTime();\n return age < SIXTY_DAYS || e.score > 0.7;\n });\n pruned = before - k.entries.length;\n\n // Remove error patterns not seen in 30+ days\n // v5.5.8: ALSO remove unresolved patterns not seen in 7+ days. These have\n // stopped recurring (no count increment for a week) so they're stale signal,\n // not a current problem. Was inflating Curiosity's `unresolvedErrorPatterns`\n // count to 139 with build-noise/test-noise from 2-3 weeks ago.\n const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1000;\n for (const [pattern, info] of Object.entries(k.errorPatterns)) {\n const ageMs = now - new Date(info.lastSeen).getTime();\n if (ageMs > THIRTY_DAYS) {\n delete k.errorPatterns[pattern];\n pruned++;\n } else if (ageMs > SEVEN_DAYS && !info.resolution) {\n // Unresolved + dormant ≥7d → stale, not real\n delete k.errorPatterns[pattern];\n pruned++;\n }\n }\n\n if (pruned > 0 || decayed > 0) {\n doSave();\n logger.info(COMPONENT, `Memory staleness check: ${pruned} pruned, ${decayed} strategies decayed`);\n }\n\n return { pruned, decayed };\n}\n\n/**\n * v4.9.0-local.9: one-time cleanup of \"error patterns\" that are actually\n * file content dumps. Retroactively applies the same classifyErrorPattern\n * logic that now runs on record. Collapses dozens of sliced file-content\n * entries into a single rollup per file.\n *\n * Returns { removed, collapsedInto } where `removed` is the number of raw\n * entries deleted and `collapsedInto` is how many rollup entries were\n * produced (usually much smaller).\n */\nexport function pruneFileContentErrorPatterns(): { removed: number; collapsedInto: number } {\n const k = loadKnowledgeBase();\n const rollup = new Map<string, { count: number; lastSeen: string; resolution?: string }>();\n const keep: Record<string, { count: number; lastSeen: string; resolution?: string }> = {};\n let removed = 0;\n\n for (const [pattern, info] of Object.entries(k.errorPatterns)) {\n const { pattern: canonical, isFileDump } = classifyErrorPattern(pattern);\n if (isFileDump) {\n // Collapse into the rollup bucket\n const existing = rollup.get(canonical);\n if (existing) {\n existing.count += info.count;\n if (info.lastSeen > existing.lastSeen) existing.lastSeen = info.lastSeen;\n if (!existing.resolution && info.resolution) existing.resolution = info.resolution;\n } else {\n rollup.set(canonical, { count: info.count, lastSeen: info.lastSeen, resolution: info.resolution });\n }\n removed++;\n } else {\n keep[pattern] = info;\n }\n }\n // Merge rollups back in (they may collide with real entries — count-sum if so)\n for (const [canonical, info] of rollup) {\n if (keep[canonical]) {\n keep[canonical].count += info.count;\n if (info.lastSeen > keep[canonical].lastSeen) keep[canonical].lastSeen = info.lastSeen;\n } else {\n keep[canonical] = info;\n }\n }\n k.errorPatterns = keep;\n doSave();\n logger.info(COMPONENT, `[PrunePatterns] removed ${removed} file-content entries, collapsed into ${rollup.size} rollup entries`);\n return { removed, collapsedInto: rollup.size };\n}\n\n/** Get learning summary for the system prompt */\nexport function getLearningContext(): string {\n const k = loadKnowledgeBase();\n const parts: string[] = [];\n\n // Recent high-score knowledge\n const topEntries = k.entries\n .filter((e) => e.score > 0.6)\n .sort((a, b) => b.score - a.score)\n .slice(0, 10);\n\n if (topEntries.length > 0) {\n parts.push('Key learned facts:');\n for (const e of topEntries) {\n parts.push(`- [${e.category}] ${e.content}`);\n }\n }\n\n // Tool recommendations\n const toolRecs = getToolRecommendations();\n const bestTools = Object.entries(toolRecs)\n .filter(([_, rate]) => rate > 0.8)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 5);\n\n if (bestTools.length > 0) {\n parts.push('\\nMost reliable tools:');\n for (const [tool, rate] of bestTools) {\n parts.push(`- ${tool}: ${Math.round(rate * 100)}% success rate`);\n }\n }\n\n // Common error patterns to avoid\n const frequentErrors = Object.entries(k.errorPatterns)\n .filter(([_, info]) => info.count > 2)\n .sort((a, b) => b[1].count - a[1].count)\n .slice(0, 3);\n\n if (frequentErrors.length > 0) {\n parts.push('\\nCommon errors to avoid:');\n for (const [pattern, info] of frequentErrors) {\n parts.push(`- ${pattern.slice(0, 100)} (seen ${info.count}x)${info.resolution ? ` → Fix: ${info.resolution}` : ''}`);\n }\n }\n\n return parts.join('\\n');\n}\n\n/** Get per-tool reliability warnings/boosts for tool description injection */\nexport function getToolWarnings(): Record<string, string> {\n const k = loadKnowledgeBase();\n const warnings: Record<string, string> = {};\n\n for (const [tool, stats] of Object.entries(k.toolSuccessRates)) {\n if (stats.total < 10) continue; // Need enough data to be meaningful\n const rate = stats.success / stats.total;\n if (rate < 0.3) {\n warnings[tool] = `[LOW RELIABILITY: ${Math.round(rate * 100)}% success rate over ${stats.total} uses]`;\n } else if (rate > 0.9) {\n warnings[tool] = `[HIGHLY RELIABLE: ${Math.round(rate * 100)}% success rate]`;\n }\n }\n\n return warnings;\n}\n\n/** Record when a tool failure is resolved by using a different tool */\nexport function recordErrorResolution(errorPattern: string, resolution: string): void {\n const k = loadKnowledgeBase();\n const pattern = errorPattern.slice(0, 200);\n if (k.errorPatterns[pattern]) {\n k.errorPatterns[pattern].resolution = resolution;\n } else {\n k.errorPatterns[pattern] = { count: 1, lastSeen: new Date().toISOString(), resolution };\n }\n debouncedSave();\n}\n\n/** Get stats about the learning system */\nexport function getLearningStats(): {\n knowledgeEntries: number;\n toolsTracked: number;\n errorPatterns: number;\n /**\n * v4.10.0-local fix: true count of UNRESOLVED patterns, used by the\n * curiosity drive. Prior behavior was to report `errorPatterns` (total)\n * as if it were unresolved — meaning marking patterns as resolved did\n * nothing to the drive signal. Now separate field.\n */\n unresolvedErrorPatterns: number;\n corrections: number;\n insights: number;\n strategies: number;\n taskTypes: number;\n} {\n const k = loadKnowledgeBase();\n const unresolvedErrorPatterns = Object.values(k.errorPatterns).filter(v => !v.resolution).length;\n return {\n knowledgeEntries: k.entries.length,\n toolsTracked: Object.keys(k.toolSuccessRates).length,\n errorPatterns: Object.keys(k.errorPatterns).length,\n unresolvedErrorPatterns,\n corrections: k.userCorrections.length,\n insights: k.conversationInsights.length,\n strategies: k.strategies.length,\n taskTypes: Object.keys(k.toolPreferencesByType).length,\n };\n}\n\n// ── Phase 4: Active Learning ──────────────────────────────────────\n\n/** Classify a message into a task type for preference tracking */\nexport function classifyTaskType(message: string): string {\n const lower = message.toLowerCase();\n if (/\\b(code|function|class|typescript|python|script|debug|compile|build)\\b/.test(lower)) return 'coding';\n if (/\\b(search|research|find|look up|investigate|compare)\\b/.test(lower)) return 'research';\n if (/\\b(analy[sz]e|data|csv|chart|graph|statistics|metrics)\\b/.test(lower)) return 'analysis';\n if (/\\b(write|draft|blog|article|email|message|story)\\b/.test(lower)) return 'writing';\n if (/\\b(deploy|server|docker|kubernetes|ci|cd|infrastructure)\\b/.test(lower)) return 'devops';\n if (/\\b(file|folder|directory|rename|move|copy|delete)\\b/.test(lower)) return 'filesystem';\n if (/\\b(schedule|cron|automat|workflow|remind)\\b/.test(lower)) return 'automation';\n return 'general';\n}\n\n/** Record a tool result against a specific task type */\nexport function recordToolPreference(toolName: string, taskType: string, success: boolean): void {\n const k = loadKnowledgeBase();\n if (!k.toolPreferencesByType[taskType]) {\n k.toolPreferencesByType[taskType] = {};\n }\n if (!k.toolPreferencesByType[taskType][toolName]) {\n k.toolPreferencesByType[taskType][toolName] = { success: 0, total: 0 };\n }\n\n k.toolPreferencesByType[taskType][toolName].total++;\n if (success) k.toolPreferencesByType[taskType][toolName].success++;\n debouncedSave();\n}\n\n/** Get ranked tool preferences for a task type */\nexport function getToolPreferences(taskType: string): ToolPreference[] {\n const k = loadKnowledgeBase();\n const prefs = k.toolPreferencesByType[taskType];\n if (!prefs) return [];\n\n return Object.entries(prefs)\n .filter(([, stats]) => stats.total >= 3) // Minimum sample size\n .map(([tool, stats]) => ({\n tool,\n successRate: stats.success / stats.total,\n totalUses: stats.total,\n }))\n .sort((a, b) => b.successRate - a.successRate);\n}\n\n/** Generate human-readable preference hints from collected tool-preference data */\nexport function getLearnedPreferenceHints(taskType: string): string | null {\n const prefs = getToolPreferences(taskType);\n if (prefs.length < 2) return null;\n\n const lines: string[] = [];\n const FILE_TOOLS = ['read_file', 'write_file', 'edit_file', 'list_dir'];\n const shellPref = prefs.find(p => p.tool === 'shell');\n\n // Find cases where shell has lower success than dedicated tools\n for (const dt of prefs.filter(p => FILE_TOOLS.includes(p.tool))) {\n if (shellPref && dt.successRate > shellPref.successRate && dt.totalUses >= 3) {\n lines.push(\n `prefer ${dt.tool} (${Math.round(dt.successRate * 100)}% success) ` +\n `over shell (${Math.round(shellPref.successRate * 100)}% success)`,\n );\n }\n }\n\n // Surface top tools for this task type if no shell comparison available\n if (lines.length === 0) {\n const top3 = prefs.slice(0, 3);\n if (top3.length >= 2) {\n lines.push(`best tools: ${top3.map(p => `${p.tool} (${Math.round(p.successRate * 100)}%)`).join(', ')}`);\n }\n }\n\n return lines.length > 0 ? `For ${taskType} tasks: ${lines.join('; ')}` : null;\n}\n\n/** Record a successful strategy for future reference */\nexport function recordStrategy(\n message: string,\n toolsUsed: string[],\n roundCount: number,\n success: boolean,\n toolSequence?: string[],\n): void {\n const k = loadKnowledgeBase();\n const taskType = classifyTaskType(message);\n\n // Check if a similar sequence already exists — merge instead of duplicating\n if (success && toolSequence && toolSequence.length > 0) {\n const seqKey = toolSequence.join('→');\n const existing = k.strategies.find(\n s => s.success && s.taskType === taskType && s.toolSequence?.join('→') === seqKey,\n );\n if (existing) {\n existing.successCount = (existing.successCount || 1) + 1;\n existing.timestamp = new Date().toISOString();\n debouncedSave();\n return;\n }\n }\n\n const entry: StrategyEntry = {\n pattern: message.slice(0, 200),\n toolsUsed: [...new Set(toolsUsed)],\n toolSequence: toolSequence?.slice(0, 20), // Cap sequence length\n taskType,\n roundCount,\n success,\n successCount: success ? 1 : 0,\n timestamp: new Date().toISOString(),\n };\n\n k.strategies.push(entry);\n invalidateStrategyCaches();\n\n // Evict old/failed strategies to keep at 200 max\n if (k.strategies.length > 200) {\n // Sort: keep high-success + recent, evict failed + old + low-success\n k.strategies.sort((a, b) => {\n if (a.success !== b.success) return a.success ? -1 : 1;\n const aCount = a.successCount || 1;\n const bCount = b.successCount || 1;\n if (aCount !== bCount) return bCount - aCount;\n return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();\n });\n k.strategies = k.strategies.slice(0, 200);\n }\n\n debouncedSave();\n}\n\n/** Record outcome of an applied strategy (feedback loop) */\nexport function recordStrategyOutcome(\n taskType: string,\n toolSequence: string[],\n succeeded: boolean,\n): void {\n const k = loadKnowledgeBase();\n const seqKey = toolSequence.join('→');\n const match = k.strategies.find(\n s => s.taskType === taskType && s.toolSequence?.join('→') === seqKey,\n );\n if (!match) return;\n\n if (succeeded) {\n match.successCount = (match.successCount || 1) + 1;\n match.lastValidated = new Date().toISOString();\n } else {\n match.failCount = (match.failCount || 0) + 1;\n }\n\n // High-fail strategies get marked as unsuccessful\n const becameUnsuccessful = (match.failCount || 0) > (match.successCount || 1) && match.success;\n if (becameUnsuccessful) {\n match.success = false;\n }\n\n if (becameUnsuccessful) invalidateStrategyCaches();\n debouncedSave();\n}\n\n/** Decay unvalidated strategies — called on every getStrategyHints */\nfunction decayStrategies(strategies: StrategyEntry[]): boolean {\n const now = Date.now();\n let changed = false;\n\n for (const s of strategies) {\n const lastValidated = s.lastValidated\n ? new Date(s.lastValidated).getTime()\n : new Date(s.timestamp).getTime();\n const daysSinceValidated = (now - lastValidated) / 86400000;\n\n // Strategies not validated in 30 days: reduce successCount by 20%\n if (daysSinceValidated > 30 && (s.successCount || 1) > 1) {\n s.successCount = Math.max(1, Math.floor((s.successCount || 1) * 0.8));\n changed = true;\n }\n\n // High-fail strategies get excluded\n if ((s.failCount || 0) > (s.successCount || 1) && s.success) {\n s.success = false;\n changed = true;\n }\n }\n\n return changed;\n}\n\n/** Get strategy hints for a similar task */\nexport function getStrategyHints(message: string): string | null {\n const k = loadKnowledgeBase();\n\n // Apply decay to stale strategies\n const now = Date.now();\n if (now - lastDecayRun > DECAY_INTERVAL_MS) {\n lastDecayRun = now;\n if (decayStrategies(k.strategies)) {\n debouncedSave();\n invalidateStrategyCaches();\n }\n }\n if (k.strategies.length === 0) return null;\n\n const taskType = classifyTaskType(message);\n const words = new Set(message.toLowerCase().split(/\\s+/).filter(w => w.length > 3));\n if (words.size === 0) return null;\n\n const successfulStrategies = successfulStrategiesCache ?? (successfulStrategiesCache = k.strategies.filter(s => s.success));\n if (successfulStrategies.length === 0) return null;\n\n // Score strategies by: task type match + keyword overlap + success count\n let bestMatch: StrategyEntry | null = null;\n let bestScore = 0;\n\n for (const strategy of successfulStrategies) {\n const patternWords = getPatternWords(strategy.pattern);\n let overlap = 0;\n for (const w of words) {\n if (patternWords.has(w)) overlap++;\n }\n const keywordScore = overlap / Math.max(words.size, patternWords.size);\n\n // Task type match bonus (0.2)\n const typeBonus = strategy.taskType === taskType ? 0.2 : 0;\n\n // Success count bonus (normalized, max 0.15)\n const countBonus = Math.min((strategy.successCount || 1) / 10, 0.15);\n\n const totalScore = keywordScore + typeBonus + countBonus;\n if (totalScore > bestScore && keywordScore > 0.15) {\n bestScore = totalScore;\n bestMatch = strategy;\n }\n }\n\n if (!bestMatch) return null;\n\n // Prefer showing the ordered sequence if available\n const toolInfo = bestMatch.toolSequence && bestMatch.toolSequence.length > 0\n ? bestMatch.toolSequence.join(' → ')\n : bestMatch.toolsUsed.join(', ');\n\n const countInfo = (bestMatch.successCount || 1) > 1\n ? ` (succeeded ${bestMatch.successCount}x)`\n : '';\n\n return `For similar ${bestMatch.taskType || 'general'} tasks, a proven tool sequence: ${toolInfo} (${bestMatch.roundCount} rounds)${countInfo}.`;\n}\n\n/** Get error resolution if a known pattern matches */\nexport function getErrorResolution(error: string): string | null {\n const k = loadKnowledgeBase();\n const errorLower = error.toLowerCase();\n\n for (const [pattern, info] of Object.entries(k.errorPatterns)) {\n if (info.resolution && errorLower.includes(pattern.toLowerCase().slice(0, 50))) {\n return info.resolution;\n }\n }\n return null;\n}\n"],"mappings":"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","names":[]}
|
package/dist/organism/drives.js
CHANGED
|
@@ -104,7 +104,8 @@ const CURIOSITY = {
|
|
|
104
104
|
const diversitySat = typeCount <= 1 ? coverage : Math.min(coverage, balance);
|
|
105
105
|
let errorPatternSat = 1;
|
|
106
106
|
if (typeof snap.unresolvedErrorPatterns === "number" && snap.unresolvedErrorPatterns > 2) {
|
|
107
|
-
|
|
107
|
+
const n = snap.unresolvedErrorPatterns - 2;
|
|
108
|
+
errorPatternSat = clamp01(1 / (1 + n / 10));
|
|
108
109
|
}
|
|
109
110
|
const satisfaction = Math.min(diversitySat, errorPatternSat);
|
|
110
111
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/organism/drives.ts"],"sourcesContent":["/**\n * TITAN — Drive Layer (Soma organism / endocrine system)\n *\n * Five homeostatic drives. Each computes a 0-1 \"satisfaction\" from existing\n * TITAN telemetry — no new instrumentation. When satisfaction dips below the\n * drive's setpoint, pressure accumulates. Cross-drive pressure fusion (see\n * pressure.ts) eventually produces a soma_proposal for human approval.\n *\n * Gated by config.organism.enabled — this module is inert when disabled.\n *\n * DRIVES SHIPPED IN v4.0:\n * Purpose — alignment with priority-1 goals\n * Hunger — backlog size vs. throughput\n * Curiosity — task-type diversity in recent trajectories\n * Safety — budget runway + recent error rate\n * Social — stale agent fraction\n *\n * DEFERRED TO v4.1+:\n * Hygiene — needs npm test + git status shell hooks\n */\nimport { existsSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { TITAN_HOME } from '../utils/constants.js';\nimport { mkdirIfNotExists } from '../utils/helpers.js';\nimport { listGoals, getReadyTasks, type Goal } from '../agent/goals.js';\nimport { getRegisteredAgents, getBudgetPolicies, listRuns, type RegisteredAgent, type BudgetPolicy, type CPRun } from '../agent/commandPost.js';\nimport { getRecentTrajectories, type TaskTrajectory } from '../agent/trajectoryLogger.js';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'Drives';\nconst DRIVE_STATE_PATH = join(TITAN_HOME, 'drive-state.json');\n\n// ── Types ────────────────────────────────────────────────────────\n\nexport type DriveId = 'purpose' | 'hunger' | 'curiosity' | 'safety' | 'social';\n\nexport interface DriveSnapshot {\n /** Timestamp of the snapshot in epoch ms. */\n now: number;\n /** All goals from goals.ts. */\n goals: Goal[];\n /** Output of getReadyTasks() — ready-to-execute subtasks. */\n readyTasks: Array<{ goal: Goal; subtask: Goal['subtasks'][number] }>;\n /** Recent CPRun history (up to 100 most recent). */\n recentRuns: CPRun[];\n /** Active budget policies. */\n budgets: BudgetPolicy[];\n /** All registered agents. */\n agents: RegisteredAgent[];\n /** Last 100 trajectory entries. */\n trajectories: TaskTrajectory[];\n /**\n * v4.9.0: fraction of GPU VRAM in use (0–1). Undefined when no GPU\n * is attached or the orchestrator hasn't refreshed yet.\n */\n vramSaturation?: number;\n /**\n * v4.9.0: error rate across recent LLM / tool calls from the\n * gateway metrics layer (0–1). Undefined when metrics are unavailable.\n */\n telemetryErrorRate?: number;\n /** v4.9.0: total LLM + tool-call requests since gateway start. */\n telemetryTotalRequests?: number;\n /**\n * v4.9.0: count of error patterns the learning layer has accumulated\n * but not yet resolved. High count pulls Curiosity toward an\n * investigate/improve proposal.\n */\n unresolvedErrorPatterns?: number;\n /**\n * v5.3.2 (Phase 8 / Track B): timestamp of the most recent successful\n * Facebook page post in epoch ms, or null if TITAN hasn't posted yet\n * since fb-autopilot was enabled. Sourced from\n * `~/.titan/fb-autopilot-state.json`. Drives the Social-drive's\n * \"social media presence\" factor — without this, social pressure was\n * 100% about agent heartbeat staleness, which the README implied was\n * about social posting. Now both factors blend.\n */\n lastFacebookPostAt?: number | null;\n}\n\nexport interface DriveDefinition {\n id: DriveId;\n label: string;\n /** Satisfaction level below which this drive starts contributing pressure. */\n defaultSetpoint: number;\n /** Relative weight in cross-drive pressure fusion (1.0 is baseline). */\n weight: number;\n /** Pure function — computes satisfaction 0-1 from the snapshot. */\n compute: (snapshot: DriveSnapshot) => { satisfaction: number; inputs?: Record<string, unknown> };\n /** Short human-readable explanation used in prompts, UI tooltips, and activity feed. */\n describe: (satisfaction: number, inputs?: Record<string, unknown>) => string;\n}\n\nexport interface DriveState {\n id: DriveId;\n label: string;\n satisfaction: number;\n setpoint: number;\n /** 0 when satisfaction >= setpoint, else (setpoint − satisfaction) × weight. */\n pressure: number;\n weight: number;\n inputs?: Record<string, unknown>;\n description: string;\n}\n\nexport interface DriveTickResult {\n timestamp: string;\n drives: DriveState[];\n totalPressure: number;\n dominantDrives: DriveId[];\n}\n\n// ── Numeric helpers ──────────────────────────────────────────────\n\n/** Clamp to [0,1]. */\nfunction clamp01(v: number): number {\n if (!Number.isFinite(v)) return 0;\n return Math.max(0, Math.min(1, v));\n}\n\n/** Sigmoid centred on `mid` with slope `k`. Returns high → 1 when x is low. */\nfunction invertedSigmoid(x: number, mid: number, k = 1): number {\n return clamp01(1 / (1 + Math.exp(k * (x - mid))));\n}\n\n/** Gini coefficient of a count distribution. 0 = uniform, 1 = all same task. */\nfunction gini(counts: number[]): number {\n if (counts.length === 0) return 0;\n const n = counts.length;\n const sum = counts.reduce((a, b) => a + b, 0);\n if (sum === 0) return 0;\n const sorted = [...counts].sort((a, b) => a - b);\n let cum = 0;\n for (let i = 0; i < n; i++) cum += (i + 1) * sorted[i];\n return clamp01((2 * cum) / (n * sum) - (n + 1) / n);\n}\n\n// ── Drive definitions ────────────────────────────────────────────\n\nconst PURPOSE: DriveDefinition = {\n id: 'purpose',\n label: 'Purpose',\n defaultSetpoint: 0.7,\n weight: 1.4,\n compute: (snap) => {\n // Priority-1 goals tagged as high-priority. Satisfaction reflects how\n // recently any of them progressed. No priority-1 goals → satiated\n // (nothing to worry about).\n const priorityOne = snap.goals.filter(g =>\n g.status === 'active' && g.priority === 1,\n );\n if (priorityOne.length === 0) {\n return { satisfaction: 0.9, inputs: { priorityOneCount: 0 } };\n }\n const latest = Math.max(...priorityOne.map(g =>\n new Date(g.updatedAt || g.createdAt).getTime(),\n ));\n const hoursSince = Math.max(0, (snap.now - latest) / 3_600_000);\n const satisfaction = clamp01(1 - hoursSince / 24);\n return {\n satisfaction,\n inputs: { priorityOneCount: priorityOne.length, hoursSinceProgress: Math.round(hoursSince * 10) / 10 },\n };\n },\n describe: (s, inputs) => {\n const count = (inputs?.priorityOneCount as number) ?? 0;\n if (count === 0) return 'no priority-1 goals in flight';\n const hours = (inputs?.hoursSinceProgress as number) ?? 0;\n if (s < 0.3) return `${count} priority-1 goal(s) stalled — no progress in ${hours.toFixed(1)}h`;\n if (s < 0.6) return `${count} priority-1 goal(s) need attention`;\n return `${count} priority-1 goal(s) on track`;\n },\n};\n\nconst HUNGER: DriveDefinition = {\n id: 'hunger',\n label: 'Hunger',\n defaultSetpoint: 0.6,\n weight: 1.0,\n compute: (snap) => {\n const readyCount = snap.readyTasks.length;\n // Oldest ready subtask age in hours, using parent goal createdAt as proxy.\n const oldestAgeHours = snap.readyTasks.length === 0\n ? 0\n : Math.max(...snap.readyTasks.map(r =>\n (snap.now - new Date(r.goal.createdAt).getTime()) / 3_600_000,\n ));\n // Both signals independently drag satisfaction down.\n // v5.0.0: floor backlog satisfaction at 0.15 so extreme backlogs\n // (e.g. 1000+ zombie goals) don't drive hunger to absolute zero,\n // which causes SOMA to panic-propose even more goals.\n const backlogSatisfaction = Math.max(0.15, invertedSigmoid(readyCount, 5, 0.35));\n const ageSatisfaction = invertedSigmoid(oldestAgeHours, 4, 0.5);\n const satisfaction = Math.min(backlogSatisfaction, ageSatisfaction);\n return {\n satisfaction,\n inputs: { readyCount, oldestAgeHours: Math.round(oldestAgeHours * 10) / 10 },\n };\n },\n describe: (s, inputs) => {\n const count = (inputs?.readyCount as number) ?? 0;\n const age = (inputs?.oldestAgeHours as number) ?? 0;\n if (count === 0) return 'backlog empty';\n if (s < 0.3) return `backlog ${count}, oldest ${age.toFixed(1)}h — elevated`;\n if (s < 0.6) return `backlog ${count}, oldest ${age.toFixed(1)}h`;\n return `backlog ${count} — fed`;\n },\n};\n\nconst CURIOSITY: DriveDefinition = {\n id: 'curiosity',\n label: 'Curiosity',\n defaultSetpoint: 0.5,\n weight: 0.8,\n compute: (snap) => {\n // Novelty = task-type diversity across recent trajectories.\n // Few distinct task types → elevated curiosity (stale). Rich variety\n // → satiated. We compose two signals:\n // 1) coverage: how many distinct types relative to a target of 5\n // 2) balance: how evenly distributed those types are (1 − gini)\n // Satisfaction = min(coverage, balance) so either deficit pulls it\n // down. Low sample counts default to middling satisfaction.\n if (snap.trajectories.length < 5) {\n return { satisfaction: 0.6, inputs: { trajectoryCount: snap.trajectories.length } };\n }\n const typeCounts: Record<string, number> = {};\n for (const t of snap.trajectories) {\n typeCounts[t.taskType || 'unknown'] = (typeCounts[t.taskType || 'unknown'] || 0) + 1;\n }\n const typeCount = Object.keys(typeCounts).length;\n const coverage = clamp01(typeCount / 5);\n const counts = Object.values(typeCounts);\n const balance = typeCount <= 1 ? 0 : clamp01(1 - gini(counts));\n const diversitySat = typeCount <= 1 ? coverage : Math.min(coverage, balance);\n\n // v4.9.0: unresolved error patterns are a form of \"task-type\n // novelty the organism hasn't figured out yet.\" More than a\n // handful of unresolved patterns pulls Curiosity toward an\n // investigate-and-improve proposal (feeds Self-Improve pipeline).\n // Scales 0→10+ patterns linearly.\n let errorPatternSat = 1;\n if (typeof snap.unresolvedErrorPatterns === 'number' && snap.unresolvedErrorPatterns > 2) {\n errorPatternSat = clamp01(1 - (snap.unresolvedErrorPatterns - 2) / 10);\n }\n\n const satisfaction = Math.min(diversitySat, errorPatternSat);\n return {\n satisfaction,\n inputs: {\n trajectoryCount: snap.trajectories.length,\n taskTypes: typeCount,\n coverage: Math.round(coverage * 100) / 100,\n balance: Math.round(balance * 100) / 100,\n unresolvedErrorPatterns: snap.unresolvedErrorPatterns ?? 0,\n errorPatternSat: Math.round(errorPatternSat * 100) / 100,\n },\n };\n },\n describe: (s, inputs) => {\n const types = (inputs?.taskTypes as number) ?? 0;\n const patterns = (inputs?.unresolvedErrorPatterns as number) ?? 0;\n if (patterns >= 5) return `${patterns} unresolved error patterns — needs investigation`;\n if (s < 0.3) return `stuck in ${types} task type(s) — stale`;\n if (s < 0.6) return `${types} task type(s) — could use novelty`;\n return `${types} distinct task type(s) — engaged`;\n },\n};\n\nconst SAFETY: DriveDefinition = {\n id: 'safety',\n label: 'Safety',\n defaultSetpoint: 0.8,\n weight: 1.6,\n compute: (snap) => {\n // Budget runway: min runway across all enabled budgets.\n let budgetSatisfaction = 1;\n const relevantBudgets = snap.budgets.filter(b => b.enabled && b.limitUsd > 0);\n if (relevantBudgets.length > 0) {\n const runways = relevantBudgets.map(b => clamp01(1 - b.currentSpend / b.limitUsd));\n budgetSatisfaction = Math.min(...runways);\n }\n // Recent error rate from last 100 CPRuns in the last 24h.\n const dayMs = 86_400_000;\n const recent = snap.recentRuns.filter(r =>\n snap.now - new Date(r.startedAt).getTime() < dayMs,\n );\n let errorSatisfaction = 1;\n if (recent.length >= 5) {\n const errors = recent.filter(r => r.status === 'error' || r.status === 'failed').length;\n errorSatisfaction = clamp01(1 - errors / recent.length);\n }\n\n // v4.9.0: VRAM saturation above 85% presses Safety. Below 85%,\n // saturation has no effect. Scales linearly 85%–100% → sat 1→0.\n let vramSatisfaction = 1;\n if (snap.vramSaturation !== undefined) {\n if (snap.vramSaturation > 0.85) {\n vramSatisfaction = clamp01(1 - (snap.vramSaturation - 0.85) / 0.15);\n }\n }\n\n // v4.9.0: gateway-level telemetry error rate (LLM/tool calls).\n // Independent of CPRun error rate — catches tool failures that\n // never bubbled up to Command Post.\n let telemetrySatisfaction = 1;\n if (snap.telemetryErrorRate !== undefined) {\n telemetrySatisfaction = clamp01(1 - snap.telemetryErrorRate * 2);\n }\n\n // Safety is a min-aggregate — the weakest link dominates.\n const satisfaction = Math.min(\n budgetSatisfaction,\n errorSatisfaction,\n vramSatisfaction,\n telemetrySatisfaction,\n );\n return {\n satisfaction,\n inputs: {\n budgetSatisfaction: Math.round(budgetSatisfaction * 100) / 100,\n errorSatisfaction: Math.round(errorSatisfaction * 100) / 100,\n vramSatisfaction: Math.round(vramSatisfaction * 100) / 100,\n telemetrySatisfaction: Math.round(telemetrySatisfaction * 100) / 100,\n recentRunCount: recent.length,\n vramSaturationPct: snap.vramSaturation !== undefined ? Math.round(snap.vramSaturation * 100) : null,\n telemetryErrorRatePct: snap.telemetryErrorRate !== undefined ? Math.round(snap.telemetryErrorRate * 100) : null,\n },\n };\n },\n describe: (s, inputs) => {\n const budget = (inputs?.budgetSatisfaction as number) ?? 1;\n const errors = (inputs?.errorSatisfaction as number) ?? 1;\n const vram = (inputs?.vramSatisfaction as number) ?? 1;\n const tel = (inputs?.telemetrySatisfaction as number) ?? 1;\n if (budget < 0.2) return 'budget runway critical';\n if (vram < 0.4) return `VRAM saturated (${inputs?.vramSaturationPct}%) — spawns at risk`;\n if (tel < 0.5) return `gateway error rate elevated (${inputs?.telemetryErrorRatePct}%)`;\n if (errors < 0.5) return 'elevated error rate in recent runs';\n if (s < 0.6) return 'safety posture weakening';\n return 'safety posture healthy';\n },\n};\n\nconst SOCIAL: DriveDefinition = {\n id: 'social',\n label: 'Social',\n defaultSetpoint: 0.7,\n weight: 0.7,\n compute: (snap) => {\n // v4.8.1: ignore specialists that were registered but never given\n // work (`totalTasksCompleted === 0`). They have nothing to heartbeat\n // about; counting them as \"unresponsive\" was a false negative.\n const eligible = snap.agents.filter(a => (a.totalTasksCompleted ?? 0) > 0 || a.status === 'active');\n const hourMs = 3_600_000;\n\n // ── Factor 1: agent liveness (legacy) ───────────────────────\n let agentSat = 0.9; // healthy default when no eligible agents\n let stale = 0;\n if (eligible.length > 0) {\n stale = eligible.filter(a =>\n snap.now - new Date(a.lastHeartbeat).getTime() > hourMs,\n ).length;\n agentSat = clamp01(1 - stale / eligible.length);\n }\n\n // ── Factor 2: social-media presence (v5.3.2) ────────────────\n // The README promises a Social drive that asks \"should I post or\n // reply?\" — that requires the drive to actually track posting\n // cadence, not just agent heartbeat. lastFacebookPostAt is wired\n // through buildSnapshot from fb-autopilot-state.json.\n //\n // Saturates at 24h: a drought of 24h+ with no post pulls\n // satisfaction to 0; a fresh post within the last hour keeps it\n // near 1. Linear in between. If lastFacebookPostAt is null/missing\n // (autopilot never ran or never posted), we treat the gap as\n // \"long\" — encourages a first post when a user enables FB.\n const POST_DROUGHT_HOURS = 24;\n let postSat: number;\n let hoursSinceLastPost: number;\n if (snap.lastFacebookPostAt && snap.lastFacebookPostAt > 0) {\n hoursSinceLastPost = Math.max(0, (snap.now - snap.lastFacebookPostAt) / hourMs);\n postSat = clamp01(1 - hoursSinceLastPost / POST_DROUGHT_HOURS);\n } else {\n // Treat \"never posted\" as ~12h drought. Don't peg to 0 —\n // organism shouldn't fire a Soma proposal the moment FB is\n // enabled before the user has even configured anything.\n hoursSinceLastPost = POST_DROUGHT_HOURS / 2;\n postSat = 0.5;\n }\n\n // Equal-weight blend. Either factor low → drive deficits.\n const satisfaction = clamp01((agentSat + postSat) / 2);\n\n return {\n satisfaction,\n inputs: {\n totalAgents: eligible.length,\n staleAgents: stale,\n hoursSinceLastPost: Number(hoursSinceLastPost.toFixed(2)),\n agentSatisfaction: Number(agentSat.toFixed(3)),\n postSatisfaction: Number(postSat.toFixed(3)),\n },\n };\n },\n describe: (_s, inputs) => {\n const total = (inputs?.totalAgents as number) ?? 0;\n const stale = (inputs?.staleAgents as number) ?? 0;\n const hoursSince = (inputs?.hoursSinceLastPost as number) ?? 0;\n const reasons: string[] = [];\n if (stale > 0) reasons.push(`${stale}/${total} agent(s) unresponsive`);\n if (hoursSince >= 12) reasons.push(`${Math.round(hoursSince)}h since last FB post`);\n if (reasons.length === 0) return `${total} agent(s) all alive · posted recently`;\n return reasons.join(' · ');\n },\n};\n\nexport const DRIVES: DriveDefinition[] = [PURPOSE, HUNGER, CURIOSITY, SAFETY, SOCIAL];\n\n// ── Snapshot builder ─────────────────────────────────────────────\n\n/** Build a DriveSnapshot by reading current TITAN state. Synchronous —\n * all inputs are in-memory or cheap disk reads. */\nexport function buildSnapshot(): DriveSnapshot {\n const goals = listGoals();\n let readyTasks: DriveSnapshot['readyTasks'] = [];\n try { readyTasks = getReadyTasks(); } catch { /* empty */ }\n const agents = getRegisteredAgents();\n const budgets = getBudgetPolicies();\n let recentRuns: CPRun[] = [];\n try { recentRuns = listRuns(undefined, 100); } catch { /* empty */ }\n let trajectories: TaskTrajectory[] = [];\n try { trajectories = getRecentTrajectories(100); } catch { /* empty */ }\n\n // v4.9.0 — pull optional closed-loop signals. Each wrapped in try so\n // drive tick never fails if a downstream module is missing or throws.\n\n let vramSaturation: number | undefined;\n try {\n const vr = readCachedVRAMSignal();\n if (vr !== null) vramSaturation = vr;\n } catch { /* no signal */ }\n\n let telemetryErrorRate: number | undefined;\n let telemetryTotalRequests: number | undefined;\n try {\n const metrics = readCachedTelemetrySignal();\n if (metrics) {\n telemetryErrorRate = metrics.errorRate;\n telemetryTotalRequests = metrics.totalRequests;\n }\n } catch { /* no signal */ }\n\n let unresolvedErrorPatterns: number | undefined;\n try {\n const patterns = readUnresolvedErrorPatternCount();\n if (patterns !== null) unresolvedErrorPatterns = patterns;\n } catch { /* no signal */ }\n\n // v5.3.2 Track B: read fb-autopilot's last successful post timestamp so\n // the Social drive's \"social media presence\" factor has real input.\n // Best-effort: never throws — Social drive falls back to a neutral\n // \"12h drought\" when this is absent (see SOCIAL.compute).\n let lastFacebookPostAt: number | null = null;\n try {\n const fbStatePath = join(TITAN_HOME, 'fb-autopilot-state.json');\n if (existsSync(fbStatePath)) {\n const raw = readFileSync(fbStatePath, 'utf-8');\n const state = JSON.parse(raw) as { lastPostAt?: string | null };\n if (state.lastPostAt) {\n const parsed = new Date(state.lastPostAt).getTime();\n if (Number.isFinite(parsed)) lastFacebookPostAt = parsed;\n }\n }\n } catch { /* ok — autopilot state missing or malformed; fall back */ }\n\n return {\n now: Date.now(),\n goals,\n readyTasks,\n recentRuns,\n budgets,\n agents,\n trajectories,\n vramSaturation,\n telemetryErrorRate,\n telemetryTotalRequests,\n unresolvedErrorPatterns,\n lastFacebookPostAt,\n };\n}\n\n// ── v4.9.0 signal readers ──────────────────────────────────────────\n\n/**\n * Reads the VRAM orchestrator's last cached snapshot (no refresh) and\n * returns used/total saturation as 0–1. Returns null when no GPU is\n * attached or the orchestrator hasn't polled yet.\n *\n * Synchronous: buildSnapshot() is called in the drive-tick hot path\n * every 60s, and we don't want to add an async nvidia-smi probe on\n * top of the existing 10s VRAM refresh.\n */\nfunction readCachedVRAMSignal(): number | null {\n try {\n // Dynamic require-like import from the already-loaded module\n // singleton. If VRAM module hasn't been initialized (e.g., in\n // tests), just return null.\n const mod = (globalThis as unknown as { __titan_vram_last?: { freeMB?: number; totalMB?: number; usedMB?: number } }).__titan_vram_last;\n if (!mod) return null;\n const total = mod.totalMB ?? 0;\n if (!Number.isFinite(total) || total <= 0) return null;\n const used = Number.isFinite(mod.usedMB) ? mod.usedMB! : (total - (mod.freeMB ?? total));\n const pct = used / total;\n if (!Number.isFinite(pct)) return null;\n return Math.max(0, Math.min(1, pct));\n } catch {\n return null;\n }\n}\n\n/** Reads the gateway metrics layer's summary (sync, in-memory). */\nfunction readCachedTelemetrySignal(): { errorRate: number; totalRequests: number } | null {\n try {\n // Using require-style resolve so tests that mock the drives\n // module don't pull in the metrics graph.\n const mod = (globalThis as unknown as { __titan_metrics_summary?: () => { totalRequests?: number; errorRate?: number } | null }).__titan_metrics_summary;\n if (typeof mod !== 'function') return null;\n const s = mod();\n if (!s || typeof s.totalRequests !== 'number' || typeof s.errorRate !== 'number') return null;\n // Only treat the signal as meaningful once we have enough samples.\n if (s.totalRequests < 10) return null;\n return { errorRate: s.errorRate, totalRequests: s.totalRequests };\n } catch {\n return null;\n }\n}\n\n/** Reads count of unresolved error patterns from the learning layer. */\nfunction readUnresolvedErrorPatternCount(): number | null {\n try {\n const mod = (globalThis as unknown as { __titan_unresolved_error_patterns?: () => number }).__titan_unresolved_error_patterns;\n if (typeof mod !== 'function') return null;\n const n = mod();\n if (typeof n !== 'number' || !Number.isFinite(n)) return null;\n return n;\n } catch {\n return null;\n }\n}\n\n// ── Drive state computation ──────────────────────────────────────\n\n/** Compute all drive states for a given snapshot, applying per-drive\n * setpoint + weight overrides + disabled-drive filter (all from\n * config.organism.{driveSetpoints,driveWeights,disabledDrives}). */\nexport function computeAllDrives(\n snapshot: DriveSnapshot,\n setpointOverrides: Partial<Record<DriveId, number>> = {},\n weightOverrides: Partial<Record<DriveId, number>> = {},\n disabledDrives: DriveId[] = [],\n): DriveState[] {\n const out: DriveState[] = [];\n const disabled = new Set(disabledDrives);\n for (const def of DRIVES) {\n if (disabled.has(def.id)) continue;\n const { satisfaction, inputs } = def.compute(snapshot);\n const setpoint = setpointOverrides[def.id] ?? def.defaultSetpoint;\n const weight = weightOverrides[def.id] ?? def.weight;\n const pressure = satisfaction < setpoint\n ? (setpoint - satisfaction) * weight\n : 0;\n out.push({\n id: def.id,\n label: def.label,\n satisfaction: clamp01(satisfaction),\n setpoint,\n pressure,\n weight,\n inputs,\n description: def.describe(satisfaction, inputs),\n });\n }\n return out;\n}\n\n// ── Persistence ──────────────────────────────────────────────────\n\nexport interface PersistedDriveHistory {\n latest: DriveTickResult;\n /** Ring buffer of last ≤1440 ticks (~24h at 60s cadence). */\n history: Array<{ timestamp: string; satisfactions: Record<DriveId, number> }>;\n}\n\n/** Load the last-written drive state (if any). Returns null on first run. */\nexport function loadDriveHistory(): PersistedDriveHistory | null {\n if (!existsSync(DRIVE_STATE_PATH)) return null;\n try {\n return JSON.parse(readFileSync(DRIVE_STATE_PATH, 'utf-8')) as PersistedDriveHistory;\n } catch (err) {\n logger.warn(COMPONENT, `drive-state.json corrupt: ${(err as Error).message}`);\n return null;\n }\n}\n\n/** Persist the tick. Ring-buffers history to a max of 1440 entries. */\nexport function saveDriveTick(tick: DriveTickResult): void {\n try {\n mkdirIfNotExists(TITAN_HOME);\n const existing = loadDriveHistory();\n const satisfactions: Record<string, number> = {};\n for (const d of tick.drives) satisfactions[d.id] = d.satisfaction;\n const history = (existing?.history || []).concat([{\n timestamp: tick.timestamp,\n satisfactions: satisfactions as Record<DriveId, number>,\n }]);\n const trimmed = history.length > 1440 ? history.slice(-1440) : history;\n const payload: PersistedDriveHistory = { latest: tick, history: trimmed };\n writeFileSync(DRIVE_STATE_PATH, JSON.stringify(payload, null, 2), 'utf-8');\n } catch (err) {\n logger.warn(COMPONENT, `Failed to save drive state: ${(err as Error).message}`);\n }\n}\n\n// ── One-call convenience ─────────────────────────────────────────\n\n/** Build snapshot → compute drives → package as a DriveTickResult. Does NOT\n * persist; callers decide whether to save (daemon tick does; read-only API\n * endpoints don't). */\nexport function runDriveTick(\n setpointOverrides: Partial<Record<DriveId, number>> = {},\n weightOverrides: Partial<Record<DriveId, number>> = {},\n disabledDrives: DriveId[] = [],\n): DriveTickResult {\n const snapshot = buildSnapshot();\n const drives = computeAllDrives(snapshot, setpointOverrides, weightOverrides, disabledDrives);\n const totalPressure = drives.reduce((sum, d) => sum + d.pressure, 0);\n const dominantDrives = drives\n .filter(d => d.pressure > 0)\n .sort((a, b) => b.pressure - a.pressure)\n .slice(0, 2)\n .map(d => d.id);\n return {\n timestamp: new Date().toISOString(),\n drives,\n totalPressure,\n dominantDrives,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/organism/drives.ts"],"sourcesContent":["/**\n * TITAN — Drive Layer (Soma organism / endocrine system)\n *\n * Five homeostatic drives. Each computes a 0-1 \"satisfaction\" from existing\n * TITAN telemetry — no new instrumentation. When satisfaction dips below the\n * drive's setpoint, pressure accumulates. Cross-drive pressure fusion (see\n * pressure.ts) eventually produces a soma_proposal for human approval.\n *\n * Gated by config.organism.enabled — this module is inert when disabled.\n *\n * DRIVES SHIPPED IN v4.0:\n * Purpose — alignment with priority-1 goals\n * Hunger — backlog size vs. throughput\n * Curiosity — task-type diversity in recent trajectories\n * Safety — budget runway + recent error rate\n * Social — stale agent fraction\n *\n * DEFERRED TO v4.1+:\n * Hygiene — needs npm test + git status shell hooks\n */\nimport { existsSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { TITAN_HOME } from '../utils/constants.js';\nimport { mkdirIfNotExists } from '../utils/helpers.js';\nimport { listGoals, getReadyTasks, type Goal } from '../agent/goals.js';\nimport { getRegisteredAgents, getBudgetPolicies, listRuns, type RegisteredAgent, type BudgetPolicy, type CPRun } from '../agent/commandPost.js';\nimport { getRecentTrajectories, type TaskTrajectory } from '../agent/trajectoryLogger.js';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'Drives';\nconst DRIVE_STATE_PATH = join(TITAN_HOME, 'drive-state.json');\n\n// ── Types ────────────────────────────────────────────────────────\n\nexport type DriveId = 'purpose' | 'hunger' | 'curiosity' | 'safety' | 'social';\n\nexport interface DriveSnapshot {\n /** Timestamp of the snapshot in epoch ms. */\n now: number;\n /** All goals from goals.ts. */\n goals: Goal[];\n /** Output of getReadyTasks() — ready-to-execute subtasks. */\n readyTasks: Array<{ goal: Goal; subtask: Goal['subtasks'][number] }>;\n /** Recent CPRun history (up to 100 most recent). */\n recentRuns: CPRun[];\n /** Active budget policies. */\n budgets: BudgetPolicy[];\n /** All registered agents. */\n agents: RegisteredAgent[];\n /** Last 100 trajectory entries. */\n trajectories: TaskTrajectory[];\n /**\n * v4.9.0: fraction of GPU VRAM in use (0–1). Undefined when no GPU\n * is attached or the orchestrator hasn't refreshed yet.\n */\n vramSaturation?: number;\n /**\n * v4.9.0: error rate across recent LLM / tool calls from the\n * gateway metrics layer (0–1). Undefined when metrics are unavailable.\n */\n telemetryErrorRate?: number;\n /** v4.9.0: total LLM + tool-call requests since gateway start. */\n telemetryTotalRequests?: number;\n /**\n * v4.9.0: count of error patterns the learning layer has accumulated\n * but not yet resolved. High count pulls Curiosity toward an\n * investigate/improve proposal.\n */\n unresolvedErrorPatterns?: number;\n /**\n * v5.3.2 (Phase 8 / Track B): timestamp of the most recent successful\n * Facebook page post in epoch ms, or null if TITAN hasn't posted yet\n * since fb-autopilot was enabled. Sourced from\n * `~/.titan/fb-autopilot-state.json`. Drives the Social-drive's\n * \"social media presence\" factor — without this, social pressure was\n * 100% about agent heartbeat staleness, which the README implied was\n * about social posting. Now both factors blend.\n */\n lastFacebookPostAt?: number | null;\n}\n\nexport interface DriveDefinition {\n id: DriveId;\n label: string;\n /** Satisfaction level below which this drive starts contributing pressure. */\n defaultSetpoint: number;\n /** Relative weight in cross-drive pressure fusion (1.0 is baseline). */\n weight: number;\n /** Pure function — computes satisfaction 0-1 from the snapshot. */\n compute: (snapshot: DriveSnapshot) => { satisfaction: number; inputs?: Record<string, unknown> };\n /** Short human-readable explanation used in prompts, UI tooltips, and activity feed. */\n describe: (satisfaction: number, inputs?: Record<string, unknown>) => string;\n}\n\nexport interface DriveState {\n id: DriveId;\n label: string;\n satisfaction: number;\n setpoint: number;\n /** 0 when satisfaction >= setpoint, else (setpoint − satisfaction) × weight. */\n pressure: number;\n weight: number;\n inputs?: Record<string, unknown>;\n description: string;\n}\n\nexport interface DriveTickResult {\n timestamp: string;\n drives: DriveState[];\n totalPressure: number;\n dominantDrives: DriveId[];\n}\n\n// ── Numeric helpers ──────────────────────────────────────────────\n\n/** Clamp to [0,1]. */\nfunction clamp01(v: number): number {\n if (!Number.isFinite(v)) return 0;\n return Math.max(0, Math.min(1, v));\n}\n\n/** Sigmoid centred on `mid` with slope `k`. Returns high → 1 when x is low. */\nfunction invertedSigmoid(x: number, mid: number, k = 1): number {\n return clamp01(1 / (1 + Math.exp(k * (x - mid))));\n}\n\n/** Gini coefficient of a count distribution. 0 = uniform, 1 = all same task. */\nfunction gini(counts: number[]): number {\n if (counts.length === 0) return 0;\n const n = counts.length;\n const sum = counts.reduce((a, b) => a + b, 0);\n if (sum === 0) return 0;\n const sorted = [...counts].sort((a, b) => a - b);\n let cum = 0;\n for (let i = 0; i < n; i++) cum += (i + 1) * sorted[i];\n return clamp01((2 * cum) / (n * sum) - (n + 1) / n);\n}\n\n// ── Drive definitions ────────────────────────────────────────────\n\nconst PURPOSE: DriveDefinition = {\n id: 'purpose',\n label: 'Purpose',\n defaultSetpoint: 0.7,\n weight: 1.4,\n compute: (snap) => {\n // Priority-1 goals tagged as high-priority. Satisfaction reflects how\n // recently any of them progressed. No priority-1 goals → satiated\n // (nothing to worry about).\n const priorityOne = snap.goals.filter(g =>\n g.status === 'active' && g.priority === 1,\n );\n if (priorityOne.length === 0) {\n return { satisfaction: 0.9, inputs: { priorityOneCount: 0 } };\n }\n const latest = Math.max(...priorityOne.map(g =>\n new Date(g.updatedAt || g.createdAt).getTime(),\n ));\n const hoursSince = Math.max(0, (snap.now - latest) / 3_600_000);\n const satisfaction = clamp01(1 - hoursSince / 24);\n return {\n satisfaction,\n inputs: { priorityOneCount: priorityOne.length, hoursSinceProgress: Math.round(hoursSince * 10) / 10 },\n };\n },\n describe: (s, inputs) => {\n const count = (inputs?.priorityOneCount as number) ?? 0;\n if (count === 0) return 'no priority-1 goals in flight';\n const hours = (inputs?.hoursSinceProgress as number) ?? 0;\n if (s < 0.3) return `${count} priority-1 goal(s) stalled — no progress in ${hours.toFixed(1)}h`;\n if (s < 0.6) return `${count} priority-1 goal(s) need attention`;\n return `${count} priority-1 goal(s) on track`;\n },\n};\n\nconst HUNGER: DriveDefinition = {\n id: 'hunger',\n label: 'Hunger',\n defaultSetpoint: 0.6,\n weight: 1.0,\n compute: (snap) => {\n const readyCount = snap.readyTasks.length;\n // Oldest ready subtask age in hours, using parent goal createdAt as proxy.\n const oldestAgeHours = snap.readyTasks.length === 0\n ? 0\n : Math.max(...snap.readyTasks.map(r =>\n (snap.now - new Date(r.goal.createdAt).getTime()) / 3_600_000,\n ));\n // Both signals independently drag satisfaction down.\n // v5.0.0: floor backlog satisfaction at 0.15 so extreme backlogs\n // (e.g. 1000+ zombie goals) don't drive hunger to absolute zero,\n // which causes SOMA to panic-propose even more goals.\n const backlogSatisfaction = Math.max(0.15, invertedSigmoid(readyCount, 5, 0.35));\n const ageSatisfaction = invertedSigmoid(oldestAgeHours, 4, 0.5);\n const satisfaction = Math.min(backlogSatisfaction, ageSatisfaction);\n return {\n satisfaction,\n inputs: { readyCount, oldestAgeHours: Math.round(oldestAgeHours * 10) / 10 },\n };\n },\n describe: (s, inputs) => {\n const count = (inputs?.readyCount as number) ?? 0;\n const age = (inputs?.oldestAgeHours as number) ?? 0;\n if (count === 0) return 'backlog empty';\n if (s < 0.3) return `backlog ${count}, oldest ${age.toFixed(1)}h — elevated`;\n if (s < 0.6) return `backlog ${count}, oldest ${age.toFixed(1)}h`;\n return `backlog ${count} — fed`;\n },\n};\n\nconst CURIOSITY: DriveDefinition = {\n id: 'curiosity',\n label: 'Curiosity',\n defaultSetpoint: 0.5,\n weight: 0.8,\n compute: (snap) => {\n // Novelty = task-type diversity across recent trajectories.\n // Few distinct task types → elevated curiosity (stale). Rich variety\n // → satiated. We compose two signals:\n // 1) coverage: how many distinct types relative to a target of 5\n // 2) balance: how evenly distributed those types are (1 − gini)\n // Satisfaction = min(coverage, balance) so either deficit pulls it\n // down. Low sample counts default to middling satisfaction.\n if (snap.trajectories.length < 5) {\n return { satisfaction: 0.6, inputs: { trajectoryCount: snap.trajectories.length } };\n }\n const typeCounts: Record<string, number> = {};\n for (const t of snap.trajectories) {\n typeCounts[t.taskType || 'unknown'] = (typeCounts[t.taskType || 'unknown'] || 0) + 1;\n }\n const typeCount = Object.keys(typeCounts).length;\n const coverage = clamp01(typeCount / 5);\n const counts = Object.values(typeCounts);\n const balance = typeCount <= 1 ? 0 : clamp01(1 - gini(counts));\n const diversitySat = typeCount <= 1 ? coverage : Math.min(coverage, balance);\n\n // v4.9.0: unresolved error patterns are a form of \"task-type\n // novelty the organism hasn't figured out yet.\" More than a\n // handful of unresolved patterns pulls Curiosity toward an\n // investigate-and-improve proposal (feeds Self-Improve pipeline).\n //\n // v5.5.8: smoothed curve. The original `1 - (n - 2) / 10`\n // saturated to 0 at just 12 patterns and stayed there\n // indefinitely, which made Curiosity satisfaction zero even\n // when the patterns were stale build-noise. Use a softer\n // logarithmic shape: satisfaction halves every ~10 patterns\n // but never hits zero. 12 patterns → ~0.59, 50 → ~0.29,\n // 139 → ~0.16. Self-Improve pipeline still triggers via\n // pressure but Curiosity isn't permanently flat-lined.\n let errorPatternSat = 1;\n if (typeof snap.unresolvedErrorPatterns === 'number' && snap.unresolvedErrorPatterns > 2) {\n const n = snap.unresolvedErrorPatterns - 2;\n errorPatternSat = clamp01(1 / (1 + n / 10));\n }\n\n const satisfaction = Math.min(diversitySat, errorPatternSat);\n return {\n satisfaction,\n inputs: {\n trajectoryCount: snap.trajectories.length,\n taskTypes: typeCount,\n coverage: Math.round(coverage * 100) / 100,\n balance: Math.round(balance * 100) / 100,\n unresolvedErrorPatterns: snap.unresolvedErrorPatterns ?? 0,\n errorPatternSat: Math.round(errorPatternSat * 100) / 100,\n },\n };\n },\n describe: (s, inputs) => {\n const types = (inputs?.taskTypes as number) ?? 0;\n const patterns = (inputs?.unresolvedErrorPatterns as number) ?? 0;\n if (patterns >= 5) return `${patterns} unresolved error patterns — needs investigation`;\n if (s < 0.3) return `stuck in ${types} task type(s) — stale`;\n if (s < 0.6) return `${types} task type(s) — could use novelty`;\n return `${types} distinct task type(s) — engaged`;\n },\n};\n\nconst SAFETY: DriveDefinition = {\n id: 'safety',\n label: 'Safety',\n defaultSetpoint: 0.8,\n weight: 1.6,\n compute: (snap) => {\n // Budget runway: min runway across all enabled budgets.\n let budgetSatisfaction = 1;\n const relevantBudgets = snap.budgets.filter(b => b.enabled && b.limitUsd > 0);\n if (relevantBudgets.length > 0) {\n const runways = relevantBudgets.map(b => clamp01(1 - b.currentSpend / b.limitUsd));\n budgetSatisfaction = Math.min(...runways);\n }\n // Recent error rate from last 100 CPRuns in the last 24h.\n const dayMs = 86_400_000;\n const recent = snap.recentRuns.filter(r =>\n snap.now - new Date(r.startedAt).getTime() < dayMs,\n );\n let errorSatisfaction = 1;\n if (recent.length >= 5) {\n const errors = recent.filter(r => r.status === 'error' || r.status === 'failed').length;\n errorSatisfaction = clamp01(1 - errors / recent.length);\n }\n\n // v4.9.0: VRAM saturation above 85% presses Safety. Below 85%,\n // saturation has no effect. Scales linearly 85%–100% → sat 1→0.\n let vramSatisfaction = 1;\n if (snap.vramSaturation !== undefined) {\n if (snap.vramSaturation > 0.85) {\n vramSatisfaction = clamp01(1 - (snap.vramSaturation - 0.85) / 0.15);\n }\n }\n\n // v4.9.0: gateway-level telemetry error rate (LLM/tool calls).\n // Independent of CPRun error rate — catches tool failures that\n // never bubbled up to Command Post.\n let telemetrySatisfaction = 1;\n if (snap.telemetryErrorRate !== undefined) {\n telemetrySatisfaction = clamp01(1 - snap.telemetryErrorRate * 2);\n }\n\n // Safety is a min-aggregate — the weakest link dominates.\n const satisfaction = Math.min(\n budgetSatisfaction,\n errorSatisfaction,\n vramSatisfaction,\n telemetrySatisfaction,\n );\n return {\n satisfaction,\n inputs: {\n budgetSatisfaction: Math.round(budgetSatisfaction * 100) / 100,\n errorSatisfaction: Math.round(errorSatisfaction * 100) / 100,\n vramSatisfaction: Math.round(vramSatisfaction * 100) / 100,\n telemetrySatisfaction: Math.round(telemetrySatisfaction * 100) / 100,\n recentRunCount: recent.length,\n vramSaturationPct: snap.vramSaturation !== undefined ? Math.round(snap.vramSaturation * 100) : null,\n telemetryErrorRatePct: snap.telemetryErrorRate !== undefined ? Math.round(snap.telemetryErrorRate * 100) : null,\n },\n };\n },\n describe: (s, inputs) => {\n const budget = (inputs?.budgetSatisfaction as number) ?? 1;\n const errors = (inputs?.errorSatisfaction as number) ?? 1;\n const vram = (inputs?.vramSatisfaction as number) ?? 1;\n const tel = (inputs?.telemetrySatisfaction as number) ?? 1;\n if (budget < 0.2) return 'budget runway critical';\n if (vram < 0.4) return `VRAM saturated (${inputs?.vramSaturationPct}%) — spawns at risk`;\n if (tel < 0.5) return `gateway error rate elevated (${inputs?.telemetryErrorRatePct}%)`;\n if (errors < 0.5) return 'elevated error rate in recent runs';\n if (s < 0.6) return 'safety posture weakening';\n return 'safety posture healthy';\n },\n};\n\nconst SOCIAL: DriveDefinition = {\n id: 'social',\n label: 'Social',\n defaultSetpoint: 0.7,\n weight: 0.7,\n compute: (snap) => {\n // v4.8.1: ignore specialists that were registered but never given\n // work (`totalTasksCompleted === 0`). They have nothing to heartbeat\n // about; counting them as \"unresponsive\" was a false negative.\n const eligible = snap.agents.filter(a => (a.totalTasksCompleted ?? 0) > 0 || a.status === 'active');\n const hourMs = 3_600_000;\n\n // ── Factor 1: agent liveness (legacy) ───────────────────────\n let agentSat = 0.9; // healthy default when no eligible agents\n let stale = 0;\n if (eligible.length > 0) {\n stale = eligible.filter(a =>\n snap.now - new Date(a.lastHeartbeat).getTime() > hourMs,\n ).length;\n agentSat = clamp01(1 - stale / eligible.length);\n }\n\n // ── Factor 2: social-media presence (v5.3.2) ────────────────\n // The README promises a Social drive that asks \"should I post or\n // reply?\" — that requires the drive to actually track posting\n // cadence, not just agent heartbeat. lastFacebookPostAt is wired\n // through buildSnapshot from fb-autopilot-state.json.\n //\n // Saturates at 24h: a drought of 24h+ with no post pulls\n // satisfaction to 0; a fresh post within the last hour keeps it\n // near 1. Linear in between. If lastFacebookPostAt is null/missing\n // (autopilot never ran or never posted), we treat the gap as\n // \"long\" — encourages a first post when a user enables FB.\n const POST_DROUGHT_HOURS = 24;\n let postSat: number;\n let hoursSinceLastPost: number;\n if (snap.lastFacebookPostAt && snap.lastFacebookPostAt > 0) {\n hoursSinceLastPost = Math.max(0, (snap.now - snap.lastFacebookPostAt) / hourMs);\n postSat = clamp01(1 - hoursSinceLastPost / POST_DROUGHT_HOURS);\n } else {\n // Treat \"never posted\" as ~12h drought. Don't peg to 0 —\n // organism shouldn't fire a Soma proposal the moment FB is\n // enabled before the user has even configured anything.\n hoursSinceLastPost = POST_DROUGHT_HOURS / 2;\n postSat = 0.5;\n }\n\n // Equal-weight blend. Either factor low → drive deficits.\n const satisfaction = clamp01((agentSat + postSat) / 2);\n\n return {\n satisfaction,\n inputs: {\n totalAgents: eligible.length,\n staleAgents: stale,\n hoursSinceLastPost: Number(hoursSinceLastPost.toFixed(2)),\n agentSatisfaction: Number(agentSat.toFixed(3)),\n postSatisfaction: Number(postSat.toFixed(3)),\n },\n };\n },\n describe: (_s, inputs) => {\n const total = (inputs?.totalAgents as number) ?? 0;\n const stale = (inputs?.staleAgents as number) ?? 0;\n const hoursSince = (inputs?.hoursSinceLastPost as number) ?? 0;\n const reasons: string[] = [];\n if (stale > 0) reasons.push(`${stale}/${total} agent(s) unresponsive`);\n if (hoursSince >= 12) reasons.push(`${Math.round(hoursSince)}h since last FB post`);\n if (reasons.length === 0) return `${total} agent(s) all alive · posted recently`;\n return reasons.join(' · ');\n },\n};\n\nexport const DRIVES: DriveDefinition[] = [PURPOSE, HUNGER, CURIOSITY, SAFETY, SOCIAL];\n\n// ── Snapshot builder ─────────────────────────────────────────────\n\n/** Build a DriveSnapshot by reading current TITAN state. Synchronous —\n * all inputs are in-memory or cheap disk reads. */\nexport function buildSnapshot(): DriveSnapshot {\n const goals = listGoals();\n let readyTasks: DriveSnapshot['readyTasks'] = [];\n try { readyTasks = getReadyTasks(); } catch { /* empty */ }\n const agents = getRegisteredAgents();\n const budgets = getBudgetPolicies();\n let recentRuns: CPRun[] = [];\n try { recentRuns = listRuns(undefined, 100); } catch { /* empty */ }\n let trajectories: TaskTrajectory[] = [];\n try { trajectories = getRecentTrajectories(100); } catch { /* empty */ }\n\n // v4.9.0 — pull optional closed-loop signals. Each wrapped in try so\n // drive tick never fails if a downstream module is missing or throws.\n\n let vramSaturation: number | undefined;\n try {\n const vr = readCachedVRAMSignal();\n if (vr !== null) vramSaturation = vr;\n } catch { /* no signal */ }\n\n let telemetryErrorRate: number | undefined;\n let telemetryTotalRequests: number | undefined;\n try {\n const metrics = readCachedTelemetrySignal();\n if (metrics) {\n telemetryErrorRate = metrics.errorRate;\n telemetryTotalRequests = metrics.totalRequests;\n }\n } catch { /* no signal */ }\n\n let unresolvedErrorPatterns: number | undefined;\n try {\n const patterns = readUnresolvedErrorPatternCount();\n if (patterns !== null) unresolvedErrorPatterns = patterns;\n } catch { /* no signal */ }\n\n // v5.3.2 Track B: read fb-autopilot's last successful post timestamp so\n // the Social drive's \"social media presence\" factor has real input.\n // Best-effort: never throws — Social drive falls back to a neutral\n // \"12h drought\" when this is absent (see SOCIAL.compute).\n let lastFacebookPostAt: number | null = null;\n try {\n const fbStatePath = join(TITAN_HOME, 'fb-autopilot-state.json');\n if (existsSync(fbStatePath)) {\n const raw = readFileSync(fbStatePath, 'utf-8');\n const state = JSON.parse(raw) as { lastPostAt?: string | null };\n if (state.lastPostAt) {\n const parsed = new Date(state.lastPostAt).getTime();\n if (Number.isFinite(parsed)) lastFacebookPostAt = parsed;\n }\n }\n } catch { /* ok — autopilot state missing or malformed; fall back */ }\n\n return {\n now: Date.now(),\n goals,\n readyTasks,\n recentRuns,\n budgets,\n agents,\n trajectories,\n vramSaturation,\n telemetryErrorRate,\n telemetryTotalRequests,\n unresolvedErrorPatterns,\n lastFacebookPostAt,\n };\n}\n\n// ── v4.9.0 signal readers ──────────────────────────────────────────\n\n/**\n * Reads the VRAM orchestrator's last cached snapshot (no refresh) and\n * returns used/total saturation as 0–1. Returns null when no GPU is\n * attached or the orchestrator hasn't polled yet.\n *\n * Synchronous: buildSnapshot() is called in the drive-tick hot path\n * every 60s, and we don't want to add an async nvidia-smi probe on\n * top of the existing 10s VRAM refresh.\n */\nfunction readCachedVRAMSignal(): number | null {\n try {\n // Dynamic require-like import from the already-loaded module\n // singleton. If VRAM module hasn't been initialized (e.g., in\n // tests), just return null.\n const mod = (globalThis as unknown as { __titan_vram_last?: { freeMB?: number; totalMB?: number; usedMB?: number } }).__titan_vram_last;\n if (!mod) return null;\n const total = mod.totalMB ?? 0;\n if (!Number.isFinite(total) || total <= 0) return null;\n const used = Number.isFinite(mod.usedMB) ? mod.usedMB! : (total - (mod.freeMB ?? total));\n const pct = used / total;\n if (!Number.isFinite(pct)) return null;\n return Math.max(0, Math.min(1, pct));\n } catch {\n return null;\n }\n}\n\n/** Reads the gateway metrics layer's summary (sync, in-memory). */\nfunction readCachedTelemetrySignal(): { errorRate: number; totalRequests: number } | null {\n try {\n // Using require-style resolve so tests that mock the drives\n // module don't pull in the metrics graph.\n const mod = (globalThis as unknown as { __titan_metrics_summary?: () => { totalRequests?: number; errorRate?: number } | null }).__titan_metrics_summary;\n if (typeof mod !== 'function') return null;\n const s = mod();\n if (!s || typeof s.totalRequests !== 'number' || typeof s.errorRate !== 'number') return null;\n // Only treat the signal as meaningful once we have enough samples.\n if (s.totalRequests < 10) return null;\n return { errorRate: s.errorRate, totalRequests: s.totalRequests };\n } catch {\n return null;\n }\n}\n\n/** Reads count of unresolved error patterns from the learning layer. */\nfunction readUnresolvedErrorPatternCount(): number | null {\n try {\n const mod = (globalThis as unknown as { __titan_unresolved_error_patterns?: () => number }).__titan_unresolved_error_patterns;\n if (typeof mod !== 'function') return null;\n const n = mod();\n if (typeof n !== 'number' || !Number.isFinite(n)) return null;\n return n;\n } catch {\n return null;\n }\n}\n\n// ── Drive state computation ──────────────────────────────────────\n\n/** Compute all drive states for a given snapshot, applying per-drive\n * setpoint + weight overrides + disabled-drive filter (all from\n * config.organism.{driveSetpoints,driveWeights,disabledDrives}). */\nexport function computeAllDrives(\n snapshot: DriveSnapshot,\n setpointOverrides: Partial<Record<DriveId, number>> = {},\n weightOverrides: Partial<Record<DriveId, number>> = {},\n disabledDrives: DriveId[] = [],\n): DriveState[] {\n const out: DriveState[] = [];\n const disabled = new Set(disabledDrives);\n for (const def of DRIVES) {\n if (disabled.has(def.id)) continue;\n const { satisfaction, inputs } = def.compute(snapshot);\n const setpoint = setpointOverrides[def.id] ?? def.defaultSetpoint;\n const weight = weightOverrides[def.id] ?? def.weight;\n const pressure = satisfaction < setpoint\n ? (setpoint - satisfaction) * weight\n : 0;\n out.push({\n id: def.id,\n label: def.label,\n satisfaction: clamp01(satisfaction),\n setpoint,\n pressure,\n weight,\n inputs,\n description: def.describe(satisfaction, inputs),\n });\n }\n return out;\n}\n\n// ── Persistence ──────────────────────────────────────────────────\n\nexport interface PersistedDriveHistory {\n latest: DriveTickResult;\n /** Ring buffer of last ≤1440 ticks (~24h at 60s cadence). */\n history: Array<{ timestamp: string; satisfactions: Record<DriveId, number> }>;\n}\n\n/** Load the last-written drive state (if any). Returns null on first run. */\nexport function loadDriveHistory(): PersistedDriveHistory | null {\n if (!existsSync(DRIVE_STATE_PATH)) return null;\n try {\n return JSON.parse(readFileSync(DRIVE_STATE_PATH, 'utf-8')) as PersistedDriveHistory;\n } catch (err) {\n logger.warn(COMPONENT, `drive-state.json corrupt: ${(err as Error).message}`);\n return null;\n }\n}\n\n/** Persist the tick. Ring-buffers history to a max of 1440 entries. */\nexport function saveDriveTick(tick: DriveTickResult): void {\n try {\n mkdirIfNotExists(TITAN_HOME);\n const existing = loadDriveHistory();\n const satisfactions: Record<string, number> = {};\n for (const d of tick.drives) satisfactions[d.id] = d.satisfaction;\n const history = (existing?.history || []).concat([{\n timestamp: tick.timestamp,\n satisfactions: satisfactions as Record<DriveId, number>,\n }]);\n const trimmed = history.length > 1440 ? history.slice(-1440) : history;\n const payload: PersistedDriveHistory = { latest: tick, history: trimmed };\n writeFileSync(DRIVE_STATE_PATH, JSON.stringify(payload, null, 2), 'utf-8');\n } catch (err) {\n logger.warn(COMPONENT, `Failed to save drive state: ${(err as Error).message}`);\n }\n}\n\n// ── One-call convenience ─────────────────────────────────────────\n\n/** Build snapshot → compute drives → package as a DriveTickResult. Does NOT\n * persist; callers decide whether to save (daemon tick does; read-only API\n * endpoints don't). */\nexport function runDriveTick(\n setpointOverrides: Partial<Record<DriveId, number>> = {},\n weightOverrides: Partial<Record<DriveId, number>> = {},\n disabledDrives: DriveId[] = [],\n): DriveTickResult {\n const snapshot = buildSnapshot();\n const drives = computeAllDrives(snapshot, setpointOverrides, weightOverrides, disabledDrives);\n const totalPressure = drives.reduce((sum, d) => sum + d.pressure, 0);\n const dominantDrives = drives\n .filter(d => d.pressure > 0)\n .sort((a, b) => b.pressure - a.pressure)\n .slice(0, 2)\n .map(d => d.id);\n return {\n timestamp: new Date().toISOString(),\n drives,\n totalPressure,\n dominantDrives,\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/utils/constants.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
const TITAN_VERSION = "5.5.
|
|
4
|
+
const TITAN_VERSION = "5.5.8";
|
|
5
5
|
const TITAN_CODENAME = "Spacewalk";
|
|
6
6
|
const TITAN_NAME = "TITAN";
|
|
7
7
|
const TITAN_FULL_NAME = "The Intelligent Task Automation Network";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/constants.ts"],"sourcesContent":["/**\n * TITAN Constants\n */\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport const TITAN_VERSION = '5.5.
|
|
1
|
+
{"version":3,"sources":["../../src/utils/constants.ts"],"sourcesContent":["/**\n * TITAN Constants\n */\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport const TITAN_VERSION = '5.5.8';\nexport const TITAN_CODENAME = 'Spacewalk';\nexport const TITAN_NAME = 'TITAN';\nexport const TITAN_FULL_NAME = 'The Intelligent Task Automation Network';\nexport const TITAN_ASCII_LOGO = `\n╔══════════════════════════════════════════════════════╗\n║ ║\n║ ████████╗██╗████████╗ █████╗ ███╗ ██╗ ║\n║ ██║ ██║ ██║ ██╔══██╗████╗ ██║ ║\n║ ██║ ██║ ██║ ███████║██╔██╗ ██║ ║\n║ ██║ ██║ ██║ ██╔══██║██║╚██╗██║ ║\n║ ██║ ██║ ██║ ██║ ██║██║ ╚████║ ║\n║ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ║\n║ ║\n║ The Intelligent Task Automation Network ║\n║ v${TITAN_VERSION} • by Tony Elliott ║\n╚══════════════════════════════════════════════════════╝`;\n\n// Paths\n// Hunt Finding #03 (2026-04-14): honor TITAN_HOME env var if set.\n// Previously this was hardcoded to `~/.titan`, which meant:\n// - Docker containers couldn't override the config path\n// - Shared machines couldn't isolate per-user state\n// - Test fixtures couldn't run against an isolated home\n// - The systemd unit's `Environment=TITAN_HOME=...` was silently ignored\n// The env var is read once at module load (constants are resolved at import time).\n// If TITAN_HOME starts with `~/`, expand it to the user's home dir.\nfunction resolveTitanHome(): string {\n const envHome = process.env.TITAN_HOME;\n if (envHome && envHome.trim().length > 0) {\n const trimmed = envHome.trim();\n if (trimmed.startsWith('~/')) {\n return join(homedir(), trimmed.slice(2));\n }\n if (trimmed === '~') {\n return homedir();\n }\n return trimmed;\n }\n return join(homedir(), '.titan');\n}\nexport const TITAN_HOME = resolveTitanHome();\nexport const TITAN_CONFIG_PATH = join(TITAN_HOME, 'titan.json');\nexport const TITAN_DB_PATH = join(TITAN_HOME, 'titan.db');\nexport const TITAN_WORKSPACE = join(TITAN_HOME, 'workspace');\nexport const TITAN_SKILLS_DIR = join(TITAN_WORKSPACE, 'skills');\nexport const TITAN_LOGS_DIR = join(TITAN_HOME, 'logs');\nexport const TITAN_MEMORY_DIR = join(TITAN_HOME, 'memory');\n\n// Workspace prompt files (injected into agent context)\nexport const AGENTS_MD = join(TITAN_WORKSPACE, 'AGENTS.md');\nexport const SOUL_MD = join(TITAN_WORKSPACE, 'SOUL.md');\nexport const TOOLS_MD = join(TITAN_WORKSPACE, 'TOOLS.md');\nexport const TITAN_MD_FILENAME = 'TITAN.md';\nexport const AUTOPILOT_MD = join(TITAN_HOME, 'AUTOPILOT.md');\nexport const AUTOPILOT_RUNS_PATH = join(TITAN_HOME, 'autopilot-runs.jsonl');\nexport const TITAN_CREDENTIALS_DIR = join(TITAN_HOME, 'credentials');\n\n// Income & lead tracking\nexport const INCOME_LEDGER_PATH = join(TITAN_HOME, 'income-ledger.jsonl');\nexport const FREELANCE_LEADS_PATH = join(TITAN_HOME, 'freelance-leads.jsonl');\nexport const FREELANCE_PROFILE_PATH = join(TITAN_HOME, 'freelance-profile.json');\nexport const LEADS_PATH = join(TITAN_HOME, 'leads.jsonl');\nexport const TELEMETRY_EVENTS_PATH = join(TITAN_HOME, 'telemetry-events.jsonl');\nexport const SOMADRIVE_STATE_PATH = join(TITAN_HOME, 'soma-drive-state.json');\nexport const ACTIVITY_LOG_PATH = join(TITAN_HOME, 'activity-log.jsonl');\n\n// Gateway defaults\nexport const DEFAULT_GATEWAY_HOST = '0.0.0.0';\nexport const DEFAULT_GATEWAY_PORT = 48420;\nexport const DEFAULT_WEB_PORT = 48421;\n\n// Agent defaults\nexport const DEFAULT_MODEL = 'anthropic/claude-sonnet-4-20250514';\n/** v5.4.1: User-preference ceiling. Providers clamp per-model via\n * clampMaxTokens() so this can be high without causing 400s on\n * capped endpoints (e.g. Claude Sonnet 4 8K, Cohere 4K). */\nexport const DEFAULT_MAX_TOKENS = 200000;\nexport const DEFAULT_TEMPERATURE = 0.7;\nexport const MAX_CONTEXT_MESSAGES = 50;\nexport const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\n// Security\nexport const DEFAULT_SANDBOX_MODE = 'host';\n/** Default allowed tools. Empty = allow ALL registered tools.\n * Use security.deniedTools to block specific tools instead. */\nexport const ALLOWED_TOOLS_DEFAULT: string[] = [];\nexport const DENIED_TOOLS_DEFAULT: string[] = [];\n"],"mappings":";AAGA,SAAS,eAAe;AACxB,SAAS,YAAY;AAEd,MAAM,gBAAgB;AACtB,MAAM,iBAAiB;AACvB,MAAM,aAAa;AACnB,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAW1B,aAAa;AAAA;AAYnB,SAAS,mBAA2B;AAChC,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,WAAW,QAAQ,KAAK,EAAE,SAAS,GAAG;AACtC,UAAM,UAAU,QAAQ,KAAK;AAC7B,QAAI,QAAQ,WAAW,IAAI,GAAG;AAC1B,aAAO,KAAK,QAAQ,GAAG,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC3C;AACA,QAAI,YAAY,KAAK;AACjB,aAAO,QAAQ;AAAA,IACnB;AACA,WAAO;AAAA,EACX;AACA,SAAO,KAAK,QAAQ,GAAG,QAAQ;AACnC;AACO,MAAM,aAAa,iBAAiB;AACpC,MAAM,oBAAoB,KAAK,YAAY,YAAY;AACvD,MAAM,gBAAgB,KAAK,YAAY,UAAU;AACjD,MAAM,kBAAkB,KAAK,YAAY,WAAW;AACpD,MAAM,mBAAmB,KAAK,iBAAiB,QAAQ;AACvD,MAAM,iBAAiB,KAAK,YAAY,MAAM;AAC9C,MAAM,mBAAmB,KAAK,YAAY,QAAQ;AAGlD,MAAM,YAAY,KAAK,iBAAiB,WAAW;AACnD,MAAM,UAAU,KAAK,iBAAiB,SAAS;AAC/C,MAAM,WAAW,KAAK,iBAAiB,UAAU;AACjD,MAAM,oBAAoB;AAC1B,MAAM,eAAe,KAAK,YAAY,cAAc;AACpD,MAAM,sBAAsB,KAAK,YAAY,sBAAsB;AACnE,MAAM,wBAAwB,KAAK,YAAY,aAAa;AAG5D,MAAM,qBAAqB,KAAK,YAAY,qBAAqB;AACjE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,yBAAyB,KAAK,YAAY,wBAAwB;AACxE,MAAM,aAAa,KAAK,YAAY,aAAa;AACjD,MAAM,wBAAwB,KAAK,YAAY,wBAAwB;AACvE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,oBAAoB,KAAK,YAAY,oBAAoB;AAG/D,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,mBAAmB;AAGzB,MAAM,gBAAgB;AAItB,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB,KAAK,KAAK;AAGrC,MAAM,uBAAuB;AAG7B,MAAM,wBAAkC,CAAC;AACzC,MAAM,uBAAiC,CAAC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "titan-agent",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.8",
|
|
4
4
|
"description": "TITAN — Autonomous AI agent framework with self-improvement, multi-agent orchestration, 36 LLM providers, 16 channel adapters, GPU VRAM management, mesh networking, LiveKit voice, TITAN-Soma homeostatic drives, and a React Mission Control dashboard. Open-source, TypeScript, MIT licensed.",
|
|
5
5
|
"author": "Tony Elliott (https://github.com/Djtony707)",
|
|
6
6
|
"repository": {
|