obol-ai 0.3.7 → 0.3.9

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/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## 0.3.8
2
+ - override haiku to sonnet when recent history has tool use
3
+
1
4
  ## 0.3.7
2
5
  - add impulse system, fix haiku context, enforce code block formatting
3
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "obol-ai",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Self-evolving AI assistant that learns, remembers, and acts on its own. Persistent vector memory, self-rewriting personality, proactive heartbeats.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/analysis.js CHANGED
@@ -124,10 +124,10 @@ async function structureReport(client, report, scheduler, patterns, chatId, time
124
124
  items: {
125
125
  type: 'object',
126
126
  properties: {
127
- key: { type: 'string', description: 'Stable identifier e.g. "timing.active_hours"' },
127
+ key: { type: 'string', description: 'Stable dot-notation identifier for this pattern, e.g. "timing.active_hours", "mood.stress_signals", "humor.style"' },
128
128
  dimension: { type: 'string', enum: ['timing', 'mood', 'humor', 'engagement', 'communication', 'topics'] },
129
- summary: { type: 'string', description: 'Human-readable statement e.g. "Usually active 7-10pm"' },
130
- data: { type: 'object', description: 'Structured supporting data' },
129
+ summary: { type: 'string', description: 'Factual observation about the user, e.g. "Most active between 7-10pm on weekdays", "Uses sarcasm and dry humor when relaxed"' },
130
+ data: { type: 'object', description: 'Factual evidence only. Examples: {"peak_hours":["19:00-22:00"],"peak_days":["mon","wed","fri"]} or {"preferred_topics":["crypto","music"]} or {"avg_message_length":"short","uses_caps":false}. Never put notes, commentary, or meta-analysis here.' },
131
131
  confidence: { type: 'number', description: '0-1' },
132
132
  },
133
133
  required: ['key', 'dimension', 'summary', 'confidence'],
@@ -139,9 +139,11 @@ async function structureReport(client, report, scheduler, patterns, chatId, time
139
139
  }];
140
140
 
141
141
  try {
142
+ const patternGuidance = `Extract behavioral patterns about this user from the report. Each pattern must be a factual observation about the user's behavior — not notes about your analysis process. If you see the same pattern in the existing list, reuse its exact key and update the summary/confidence. Skip patterns already at confidence >0.8 unless new evidence contradicts them.`;
143
+
142
144
  const system = formattedPatterns
143
- ? `Existing behavioral patterns for this user:\n${formattedPatterns}\n\n---\n\nConvert this analytical report into structured data using the save_analysis tool. Use existing patterns to calibrate confidence scores (higher if confirming, consider skipping if already well-established at >0.8). Flag contradictions in pattern data.`
144
- : 'Convert this analytical report into structured data using the save_analysis tool. Extract all follow-ups and patterns mentioned.';
145
+ ? `Existing behavioral patterns for this user:\n${formattedPatterns}\n\n---\n\n${patternGuidance}`
146
+ : `Convert this analytical report into structured data using the save_analysis tool. ${patternGuidance}`;
145
147
 
146
148
  const response = await client.messages.create({
147
149
  model: 'claude-sonnet-4-6',
@@ -169,8 +171,10 @@ async function structureReport(client, report, scheduler, patterns, chatId, time
169
171
 
170
172
  for (const p of patternList) {
171
173
  if (!p.key || !p.dimension || !p.summary) continue;
172
- await patterns.upsert(p.key, p.dimension, p.summary, p.data || {}, p.confidence || 0.5).catch(e =>
173
- console.error('[analysis] Failed to upsert pattern:', e.message)
174
+ const existing = await patterns.get(p.key).catch(() => null);
175
+ const save = existing ? patterns.incrementObservation : patterns.upsert;
176
+ await save(p.key, p.dimension, p.summary, p.data || {}, p.confidence || 0.5).catch(e =>
177
+ console.error('[analysis] Failed to save pattern:', e.message)
174
178
  );
175
179
  }
176
180
 
@@ -41,6 +41,17 @@ function likelyNeedsTools(message) {
41
41
  return TOOL_PATTERNS.some(p => p.test(lower));
42
42
  }
43
43
 
44
+ function recentlyUsedTools(history) {
45
+ for (let i = history.length - 1; i >= Math.max(0, history.length - 4); i--) {
46
+ const msg = history[i];
47
+ if (msg.role === 'assistant' && Array.isArray(msg.content) &&
48
+ msg.content.some(b => b.type === 'tool_use')) {
49
+ return true;
50
+ }
51
+ }
52
+ return false;
53
+ }
54
+
44
55
  async function routeMessage(client, memory, userMessage, { vlog, onRouteDecision, onRouteUpdate, recentHistory = [], selfMemory = null }) {
45
56
  let memoryBlock = null;
46
57
  let model = null;
@@ -83,6 +94,11 @@ If recent context shows an ongoing task (sonnet/opus was just used, multi-step w
83
94
  decision.model = 'sonnet';
84
95
  }
85
96
 
97
+ if (decision.model === 'haiku' && recentlyUsedTools(recentHistory)) {
98
+ vlog('[router] haiku overridden → sonnet (recent tool use in history)');
99
+ decision.model = 'sonnet';
100
+ }
101
+
86
102
  vlog(`[router] model=${decision.model || 'sonnet'} memory=${decision.need_memory || false}${queries.length ? ` queries=${JSON.stringify(queries)}` : ''}`);
87
103
 
88
104
  onRouteDecision?.({