mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-21 22:06:42 +02:00
Marks all 7 phases as completed with commit hashes, implementation notes, and decisions made during implementation. Adds comprehensive manual testing section covering Event Bus, Projections, Companion Chat, Rituals, Goals, Memory, Correlations, and Pulse Rules — with code snippets for DevTools Console and IndexedDB inspection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1577 lines
55 KiB
Markdown
1577 lines
55 KiB
Markdown
# Mana Companion Brain — Architecture & Implementation Plan
|
|
|
|
> Vollstaendiger Umbau-Plan fuer ein zentrales Intelligenz-System ueber alle Module.
|
|
> Start mit 5 Pilot-Modulen: **Todo, Calendar, Drink, Nutriphi, Places**.
|
|
> Stand: April 2026
|
|
|
|
---
|
|
|
|
## 1. Vision
|
|
|
|
Mana hat 40+ Module, die isoliert arbeiten. Der Companion Brain verbindet sie zu einem System, das den Nutzer proaktiv begleitet — erinnert, motiviert, Muster erkennt und Zusammenhaenge zwischen Modulen herstellt. Alles lokal, privacy-first.
|
|
|
|
**Drei Saeulen:**
|
|
1. **Pulse** — Regelbasierte Nudges & Tageszusammenfassungen (kein LLM)
|
|
2. **Rituale** — Gefuehrte Routinen die Daten in Module schreiben (AI-generiert)
|
|
3. **Companion Chat** — LLM mit Tool-Zugriff auf alle Module
|
|
|
|
**Fundament:**
|
|
- Domain Event Bus (semantische Events statt CRUD-Logs)
|
|
- Projection Engine (live-reaktive Aggregation ueber alle Module)
|
|
- Goal System (moduluebergreifende Ziele mit Fortschritt)
|
|
- Semantic Memory (extrahiertes Nutzerwissen, persistent)
|
|
- Tool Layer (standardisierter LLM-Zugriff auf Module)
|
|
- Feedback Loop (Nudge-Outcomes fuer Lernfaehigkeit)
|
|
|
|
---
|
|
|
|
## 2. Architektur-Uebersicht
|
|
|
|
```
|
|
+---------------------------------------------------+
|
|
| MODULE LAYER |
|
|
| Todo - Calendar - Drink - Nutriphi - Places |
|
|
| Jedes Modul emittiert Domain Events via Stores |
|
|
+------------------------+--------------------------+
|
|
| emit()
|
|
v
|
|
+---------------------------------------------------+
|
|
| EVENT BUS |
|
|
| Typed, synchron, in-process |
|
|
| TaskCompleted - DrinkLogged - EventCreated ... |
|
|
+--+--------+--------+--------+--------+-----------+
|
|
| | | | |
|
|
v v v v v
|
|
+------+ +------+ +------+ +------+ +------+
|
|
|Event | |State | |Proj. | |Rule | |Trig- |
|
|
|Store | |Write | |Engine| |Engine| |gers |
|
|
| | |Dexie | | | | | | |
|
|
+------+ +------+ +--+---+ +--+---+ +------+
|
|
| |
|
|
+----------+--------+----------+
|
|
v v v v
|
|
+---------------------------------------------------+
|
|
| INTELLIGENCE LAYER |
|
|
| |
|
|
| +------------+ +----------+ +-------+ +--------+ |
|
|
| |Projections | | Memory | | Goals | |Feedback| |
|
|
| |DaySnapshot | | Patterns | | Meter | | Loop | |
|
|
| |Streaks | | Prefs | | Track | | Nudge | |
|
|
| |Correlations| | Context | | Link | | Outcome| |
|
|
| +-----+------+ +----+-----+ +---+---+ +---+----+ |
|
|
| | | | | |
|
|
| +--------------+-----------+---------+ |
|
|
| v |
|
|
| Context Document Generator |
|
|
| (~500 Token Nutzer-Snapshot) |
|
|
+------------------------+--------------------------+
|
|
|
|
|
v
|
|
+---------------------------------------------------+
|
|
| INTERACTION LAYER |
|
|
| |
|
|
| +----------+ +----------+ +---------+ +---------+ |
|
|
| | Pulse | | Rituale | |Companion| |Insights | |
|
|
| | Engine | | (AI-gen) | | Chat | | Cards | |
|
|
| | regelb. | | | |LLM+Tool | | | |
|
|
| +----------+ +----------+ +---------+ +---------+ |
|
|
| |
|
|
| Feedback: Nudge -> Outcome -> Memory Update |
|
|
+---------------------------------------------------+
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Domain Event System
|
|
|
|
### 3.1 Warum Domain Events statt CRUD-Logs
|
|
|
|
Aktuell loggt `_activity` nur `{ op: 'update', collection: 'tasks', recordId }`. Daraus laesst sich nicht ableiten, **was** passiert ist. Wurde der Task erledigt? Umbenannt? Verschoben? Das erzwingt Archaeologie — Felder vergleichen, Semantik raten.
|
|
|
|
Domain Events tragen Bedeutung: `TaskCompleted { taskId, title, project }` ist sofort verstaendlich fuer Projections, Rules, LLM und Mensch.
|
|
|
|
### 3.2 Event Bus Interface
|
|
|
|
**Neues File: `apps/mana/apps/web/src/lib/data/events/event-bus.ts`**
|
|
|
|
```typescript
|
|
// ── Core Types ──────────────────────────────────────
|
|
|
|
export interface DomainEvent<T extends string = string, P = unknown> {
|
|
type: T;
|
|
payload: P;
|
|
meta: EventMeta;
|
|
}
|
|
|
|
export interface EventMeta {
|
|
id: string; // crypto.randomUUID()
|
|
timestamp: string; // ISO
|
|
appId: string; // source module
|
|
collection: string; // source table
|
|
recordId: string; // affected record
|
|
userId: string; // from getEffectiveUserId()
|
|
causedBy?: string; // parent event id (for trigger chains)
|
|
}
|
|
|
|
// ── Bus Interface ───────────────────────────────────
|
|
|
|
export type EventHandler<E extends DomainEvent = DomainEvent> = (event: E) => void;
|
|
|
|
export interface EventBus {
|
|
emit(event: DomainEvent): void;
|
|
on<T extends string>(type: T, handler: EventHandler): () => void;
|
|
onAny(handler: EventHandler): () => void;
|
|
off(type: string, handler: EventHandler): void;
|
|
}
|
|
```
|
|
|
|
**Implementierung:** Einfacher synchroner Dispatcher mit async Subscribers.
|
|
- `emit()` ist synchron (blockiert Dexie-Hook nicht)
|
|
- Handlers laufen in `queueMicrotask()` — nach dem Hook, aber vor dem naechsten Frame
|
|
- Guard gegen Endlos-Loops: `_emitting` Set verhindert re-entrant emits vom selben Event-Typ
|
|
|
|
```typescript
|
|
export function createEventBus(): EventBus {
|
|
const handlers = new Map<string, Set<EventHandler>>();
|
|
const anyHandlers = new Set<EventHandler>();
|
|
|
|
return {
|
|
emit(event: DomainEvent) {
|
|
queueMicrotask(() => {
|
|
const typeHandlers = handlers.get(event.type);
|
|
if (typeHandlers) {
|
|
for (const h of typeHandlers) h(event);
|
|
}
|
|
for (const h of anyHandlers) h(event);
|
|
});
|
|
},
|
|
|
|
on(type, handler) {
|
|
if (!handlers.has(type)) handlers.set(type, new Set());
|
|
handlers.get(type)!.add(handler);
|
|
return () => handlers.get(type)?.delete(handler);
|
|
},
|
|
|
|
onAny(handler) {
|
|
anyHandlers.add(handler);
|
|
return () => anyHandlers.delete(handler);
|
|
},
|
|
|
|
off(type, handler) {
|
|
handlers.get(type)?.delete(handler);
|
|
},
|
|
};
|
|
}
|
|
|
|
// Singleton
|
|
export const eventBus = createEventBus();
|
|
```
|
|
|
|
### 3.3 Event Store
|
|
|
|
Ersetzt die `_activity`-Tabelle als primaere Quelle fuer "was ist passiert".
|
|
|
|
**Neue Dexie-Tabelle `_events`:**
|
|
```
|
|
_events: '++seq, meta.id, meta.type, meta.appId, meta.timestamp,
|
|
[meta.appId+meta.timestamp], [meta.type+meta.timestamp]'
|
|
```
|
|
|
|
Felder:
|
|
- `seq` — Auto-increment (Reihenfolge-Garantie)
|
|
- `type` — Domain Event Type (z.B. 'TaskCompleted')
|
|
- `payload` — Serialisiertes Event-Payload (verschluesselt fuer sensitive Felder)
|
|
- `meta` — EventMeta Objekt
|
|
|
|
**Retention:** 90 Tage (wie `_activity`), max 50.000 Events. Pruning via bestehender Quota-Recovery.
|
|
|
|
**Subscriber:** `eventBus.onAny()` schreibt jedes Event in `_events`.
|
|
|
|
### 3.4 Domain Events pro Modul (5 Pilot-Module)
|
|
|
|
#### Todo Events
|
|
|
|
| Event | Payload | Abgeleitet aus |
|
|
|-------|---------|----------------|
|
|
| `TaskCreated` | `{ taskId, title, dueDate?, priority?, projectId?, labelIds? }` | `tasksStore.createTask()` |
|
|
| `TaskCompleted` | `{ taskId, title, projectId?, wasOverdue: boolean }` | `tasksStore.completeTask()` |
|
|
| `TaskUncompleted` | `{ taskId, title }` | `tasksStore.uncompleteTask()` |
|
|
| `TaskUpdated` | `{ taskId, fields: string[] }` | `tasksStore.updateTask()` |
|
|
| `TaskDeleted` | `{ taskId, title }` | `tasksStore.deleteTask()` |
|
|
| `TaskRescheduled` | `{ taskId, title, oldDate?, newDate }` | `updateTask` wenn `dueDate` aendert |
|
|
| `SubtasksUpdated` | `{ taskId, total, completed }` | `tasksStore.updateSubtasks()` |
|
|
| `ReminderSet` | `{ taskId, minutesBefore, type }` | `remindersStore.createReminder()` |
|
|
|
|
#### Calendar Events
|
|
|
|
| Event | Payload | Abgeleitet aus |
|
|
|-------|---------|----------------|
|
|
| `CalendarEventCreated` | `{ eventId, title, startTime, endTime, isAllDay, isRecurring, calendarId }` | `eventsStore.createEvent()` |
|
|
| `CalendarEventUpdated` | `{ eventId, fields: string[] }` | `eventsStore.updateEvent()` |
|
|
| `CalendarEventDeleted` | `{ eventId, title, wasRecurring }` | `eventsStore.deleteEvent()` |
|
|
| `CalendarEventMoved` | `{ eventId, title, oldStart, newStart }` | `updateEvent` wenn `startTime` aendert |
|
|
|
|
#### Drink Events
|
|
|
|
| Event | Payload | Abgeleitet aus |
|
|
|-------|---------|----------------|
|
|
| `DrinkLogged` | `{ entryId, drinkType, quantityMl, name, date, time, fromPreset: boolean }` | `drinkStore.logDrink()`, `logFromPreset()` |
|
|
| `DrinkEntryDeleted` | `{ entryId, drinkType, quantityMl }` | `drinkStore.deleteEntry()` |
|
|
| `DrinkEntryUndone` | `{ entryId }` | `drinkStore.undoLastEntry()` |
|
|
| `DrinkGoalReached` | `{ date, goalMl, actualMl, drinkType: 'water' }` | Projection erkennt Zielerreichung |
|
|
|
|
#### Nutriphi Events
|
|
|
|
| Event | Payload | Abgeleitet aus |
|
|
|-------|---------|----------------|
|
|
| `MealLogged` | `{ mealId, mealType, inputType, description, calories?, protein?, date }` | `mealMutations.create()` |
|
|
| `MealFromPhotoLogged` | `{ mealId, mealType, photoMediaId, confidence, foods? }` | `mealMutations.createFromPhoto()` |
|
|
| `MealDeleted` | `{ mealId, mealType }` | `mealMutations.delete()` |
|
|
| `NutritionGoalSet` | `{ dailyCalories, dailyProtein?, dailyCarbs?, dailyFat? }` | `goalMutations.create/update()` |
|
|
| `DailyCalorieGoalReached` | `{ date, goal, actual }` | Projection erkennt Zielerreichung |
|
|
|
|
#### Places Events
|
|
|
|
| Event | Payload | Abgeleitet aus |
|
|
|-------|---------|----------------|
|
|
| `PlaceCreated` | `{ placeId, name, category?, lat, lng }` | `placesStore.createPlace()` |
|
|
| `PlaceVisited` | `{ placeId, name, visitCount }` | `placesStore.recordVisit()` |
|
|
| `LocationLogged` | `{ logId, lat, lng, placeId?, accuracy }` | `trackingStore.logNow()` |
|
|
| `TrackingStarted` | `{}` | `trackingStore.startTracking()` |
|
|
| `TrackingStopped` | `{ durationMs, logCount }` | `trackingStore.stopTracking()` |
|
|
|
|
### 3.5 Event-Emission aus Module Stores
|
|
|
|
Jeder Store bekommt `emit()`-Calls in seinen Mutations. Kein Umbau der Dexie-Hooks noetig — Events werden **im Store** emittiert, nicht im Hook.
|
|
|
|
**Warum im Store statt im Hook?** Der Hook sieht nur CRUD. Der Store kennt die Semantik. `completeTask()` weiss, dass es ein Completion ist — der Hook sieht nur `update({ completedAt })`.
|
|
|
|
**Beispiel: Todo Store nach Umbau:**
|
|
|
|
```typescript
|
|
// stores/tasks.svelte.ts
|
|
import { eventBus } from '$lib/data/events/event-bus';
|
|
|
|
export const tasksStore = {
|
|
async completeTask(id: string) {
|
|
const task = await taskTable.get(id);
|
|
if (!task) return;
|
|
const now = new Date().toISOString();
|
|
const wasOverdue = task.dueDate && task.dueDate < now.slice(0, 10);
|
|
|
|
await taskTable.update(id, { completedAt: now, updatedAt: now });
|
|
|
|
eventBus.emit({
|
|
type: 'TaskCompleted',
|
|
payload: {
|
|
taskId: id,
|
|
title: task.title, // plaintext snapshot (pre-encryption)
|
|
projectId: task.projectId,
|
|
wasOverdue,
|
|
},
|
|
meta: {
|
|
id: crypto.randomUUID(),
|
|
timestamp: now,
|
|
appId: 'todo',
|
|
collection: 'tasks',
|
|
recordId: id,
|
|
userId: getEffectiveUserId(),
|
|
},
|
|
});
|
|
},
|
|
// ... andere Mutations analog
|
|
};
|
|
```
|
|
|
|
**Konvention:** Jede Store-Mutation die einen Seiteneffekt hat, emittiert ein Event. Reine UI-State-Aenderungen (z.B. `calendarViewStore.setDate()`) emittieren nicht.
|
|
|
|
### 3.6 Event Helper fuer Module
|
|
|
|
Um Boilerplate zu reduzieren, ein `createEventEmitter` Helper:
|
|
|
|
**Neues File: `apps/mana/apps/web/src/lib/data/events/emit.ts`**
|
|
|
|
```typescript
|
|
import { eventBus } from './event-bus';
|
|
import { getEffectiveUserId } from '../current-user';
|
|
|
|
export function emitDomainEvent<P>(
|
|
type: string,
|
|
appId: string,
|
|
collection: string,
|
|
recordId: string,
|
|
payload: P,
|
|
causedBy?: string
|
|
): void {
|
|
eventBus.emit({
|
|
type,
|
|
payload,
|
|
meta: {
|
|
id: crypto.randomUUID(),
|
|
timestamp: new Date().toISOString(),
|
|
appId,
|
|
collection,
|
|
recordId,
|
|
userId: getEffectiveUserId(),
|
|
causedBy,
|
|
},
|
|
});
|
|
}
|
|
```
|
|
|
|
Aufruf im Store wird dann einzeilig:
|
|
|
|
```typescript
|
|
emitDomainEvent('TaskCompleted', 'todo', 'tasks', id, {
|
|
taskId: id, title: task.title, projectId: task.projectId, wasOverdue,
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Projection Engine
|
|
|
|
### 4.1 Prinzip
|
|
|
|
Projections sind **live-reaktive Aggregationen** ueber Modul-Daten. Sie hoeren Domain Events und aktualisieren sich inkrementell. Consumers (Pulse, Companion, Dashboard) lesen Projections — nie Rohdaten.
|
|
|
|
**Neuer Ordner: `apps/mana/apps/web/src/lib/data/projections/`**
|
|
|
|
### 4.2 DaySnapshot
|
|
|
|
Beantwortet: "Was ist heute los?"
|
|
|
|
```typescript
|
|
// projections/day-snapshot.ts
|
|
|
|
export interface DaySnapshot {
|
|
date: string; // YYYY-MM-DD
|
|
|
|
// Todo
|
|
tasks: {
|
|
total: number;
|
|
completed: number;
|
|
overdue: number;
|
|
dueToday: TaskSummary[];
|
|
};
|
|
|
|
// Calendar
|
|
events: {
|
|
upcoming: EventSummary[]; // naechste 5 Events
|
|
total: number;
|
|
nextEvent?: EventSummary;
|
|
};
|
|
|
|
// Drink
|
|
drinks: {
|
|
water: { ml: number; goal: number; percent: number };
|
|
coffee: { ml: number; count: number };
|
|
other: { ml: number; count: number };
|
|
total: { ml: number; count: number };
|
|
};
|
|
|
|
// Nutriphi
|
|
nutrition: {
|
|
meals: number;
|
|
calories: { actual: number; goal: number; percent: number };
|
|
protein?: { actual: number; goal: number };
|
|
};
|
|
|
|
// Places
|
|
places: {
|
|
visited: number;
|
|
currentLocation?: { lat: number; lng: number; placeName?: string };
|
|
tracking: boolean;
|
|
};
|
|
}
|
|
```
|
|
|
|
**Implementierung:** Dexie liveQueries die auf `$derived` gemapped werden. Event-Listener fuer inkrementelle Updates (z.B. `DrinkLogged` addiert direkt statt neu zu querien).
|
|
|
|
### 4.3 Streaks
|
|
|
|
Beantwortet: "Was laeuft gut, was droht zu brechen?"
|
|
|
|
```typescript
|
|
// projections/streaks.ts
|
|
|
|
export interface StreakInfo {
|
|
moduleId: string;
|
|
label: string; // "Wasser-Ziel", "Journal", "Sport"
|
|
currentStreak: number; // Tage in Folge
|
|
longestStreak: number;
|
|
lastActiveDate: string; // YYYY-MM-DD
|
|
status: 'active' | 'at_risk' | 'broken';
|
|
// active: heute oder gestern aktiv
|
|
// at_risk: gestern nicht aktiv, vorgestern schon
|
|
// broken: >1 Tag Pause
|
|
}
|
|
```
|
|
|
|
Berechnet aus: TimeBlocks + Modul-spezifische Logik (Drink: Tagesziel erreicht, Todo: mindestens 1 Task erledigt, etc.)
|
|
|
|
### 4.4 Correlations
|
|
|
|
Beantwortet: "Was haengt zusammen?"
|
|
|
|
```typescript
|
|
// projections/correlations.ts
|
|
|
|
export interface Correlation {
|
|
id: string;
|
|
factorA: { module: string; metric: string; label: string };
|
|
factorB: { module: string; metric: string; label: string };
|
|
coefficient: number; // Pearson r, -1 bis +1
|
|
pValue: number; // Statistische Signifikanz
|
|
sampleSize: number; // Anzahl Tage
|
|
direction: 'positive' | 'negative';
|
|
sentence: string; // "An Tagen mit Sport trinkst du 30% mehr Wasser"
|
|
computedAt: string;
|
|
}
|
|
```
|
|
|
|
**Berechnung:** 1x taeglich, ueber TimeBlocks + Tagesaggregate der letzten 30-90 Tage. Pearson-Korrelation zwischen Paaren. Nur Korrelationen mit |r| > 0.3 und p < 0.05 werden gespeichert.
|
|
|
|
**Metriken pro Modul:**
|
|
- Todo: tasks_completed_count, overdue_count
|
|
- Calendar: events_count, meeting_hours
|
|
- Drink: water_ml, coffee_count, goal_reached (boolean)
|
|
- Nutriphi: calories, protein, meals_count
|
|
- Places: places_visited, distance_km
|
|
|
|
### 4.5 ContactHealth (spaeter, nicht in Pilot)
|
|
|
|
Wird mit dem Contacts-Modul relevant. Trackt Kontakthaeufigkeit vs. erwartete Frequenz.
|
|
|
|
---
|
|
|
|
## 5. Goal System
|
|
|
|
### 5.1 Datenmodell
|
|
|
|
**Neue Dexie-Tabelle: `goals`**
|
|
```
|
|
goals: 'id, moduleId, status, [moduleId+status]'
|
|
```
|
|
|
|
```typescript
|
|
export interface LocalGoal {
|
|
id: string;
|
|
title: string; // "4x Sport pro Woche"
|
|
description?: string;
|
|
|
|
// Metrik-Definition
|
|
metric: GoalMetric;
|
|
target: GoalTarget;
|
|
|
|
// Verknuepfung
|
|
moduleId: string; // primaeres Modul
|
|
linkedModules: string[]; // weitere beteiligte Module
|
|
|
|
// Status
|
|
status: 'active' | 'paused' | 'completed' | 'abandoned';
|
|
|
|
// Tracking
|
|
currentValue: number; // live berechnet
|
|
currentPeriodStart: string; // Beginn der aktuellen Periode
|
|
history: GoalPeriodResult[]; // vergangene Perioden
|
|
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
deletedAt?: string;
|
|
}
|
|
|
|
export interface GoalMetric {
|
|
source: 'event_count' | 'event_sum' | 'streak_days' | 'custom';
|
|
eventType?: string; // Domain Event Type (z.B. 'DrinkLogged')
|
|
filterField?: string; // z.B. 'drinkType'
|
|
filterValue?: string; // z.B. 'water'
|
|
sumField?: string; // z.B. 'quantityMl' (fuer event_sum)
|
|
}
|
|
|
|
export interface GoalTarget {
|
|
value: number; // Zielwert
|
|
period: 'day' | 'week' | 'month';
|
|
comparison: 'gte' | 'lte' | 'eq'; // >= (Sport), <= (Kaffee), = (exakt)
|
|
}
|
|
|
|
export interface GoalPeriodResult {
|
|
periodStart: string;
|
|
periodEnd: string;
|
|
value: number;
|
|
reached: boolean;
|
|
}
|
|
```
|
|
|
|
### 5.2 Goal-Tracking via Events
|
|
|
|
Der Goal-Tracker subscribed auf den Event Bus und aktualisiert `currentValue` inkrementell:
|
|
|
|
```typescript
|
|
// Beispiel: Ziel "8 Glaeser Wasser/Tag"
|
|
// metric: { source: 'event_count', eventType: 'DrinkLogged', filterField: 'drinkType', filterValue: 'water' }
|
|
// target: { value: 8, period: 'day', comparison: 'gte' }
|
|
|
|
eventBus.on('DrinkLogged', (event) => {
|
|
if (event.payload.drinkType === 'water') {
|
|
goal.currentValue += 1;
|
|
if (goal.currentValue >= goal.target.value) {
|
|
emitDomainEvent('GoalReached', 'companion', 'goals', goal.id, {
|
|
goalId: goal.id, title: goal.title, value: goal.currentValue,
|
|
});
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
### 5.3 Vordefinierte Ziel-Templates
|
|
|
|
Fuer den Start 10-15 Templates die der Nutzer mit einem Tap aktiviert:
|
|
|
|
- 8 Glaeser Wasser/Tag (Drink, event_count, DrinkLogged, water)
|
|
- 2000 kcal/Tag (Nutriphi, event_sum, MealLogged, calories)
|
|
- 5 Tasks/Tag erledigen (Todo, event_count, TaskCompleted)
|
|
- Alle Mahlzeiten tracken (Nutriphi, event_count, MealLogged, 3/day)
|
|
- Jeden Tag einen neuen Ort besuchen (Places, event_count, PlaceVisited, 1/day)
|
|
|
|
---
|
|
|
|
## 6. Semantic Memory
|
|
|
|
### 6.1 Datenmodell
|
|
|
|
**Neue Dexie-Tabelle: `_memory`**
|
|
```
|
|
_memory: 'id, category, confidence, lastConfirmed, [category+confidence]'
|
|
```
|
|
|
|
```typescript
|
|
export interface MemoryFact {
|
|
id: string;
|
|
category: 'pattern' | 'preference' | 'relationship' | 'context';
|
|
|
|
content: string; // Menschenlesbarer Fakt
|
|
// "Trainiert typischerweise Mo/Mi/Fr abends"
|
|
// "Trinkt morgens immer zuerst Kaffee, dann Wasser"
|
|
// "Meetings haeufig Di/Do vormittags"
|
|
|
|
confidence: number; // 0.0 - 1.0
|
|
confirmations: number; // wie oft bestaetigt
|
|
contradictions: number; // wie oft widersprochen
|
|
|
|
sourceEvents: string[]; // Event-IDs die diesen Fakt stuetzen
|
|
sourceModules: string[]; // beteiligte Module
|
|
|
|
firstSeen: string; // wann erstmals erkannt
|
|
lastConfirmed: string; // letzte Bestaetigung
|
|
expiresAt?: string; // fuer temporaere Kontexte
|
|
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
deletedAt?: string;
|
|
}
|
|
```
|
|
|
|
### 6.2 Extraktion
|
|
|
|
**Zwei Wege:**
|
|
|
|
1. **Regelbasiert (kein LLM):** Pattern-Detektoren ueber Event-Stream:
|
|
- Wiederholungs-Detektor: "3x in 2 Wochen am Montag trainiert → Pattern: trainiert montags"
|
|
- Zeitfenster-Detektor: "Tasks werden zu 80% zwischen 09-12 Uhr erledigt → Preference: Morgen-Produktivitaet"
|
|
- Sequenz-Detektor: "Kaffee wird immer vor dem ersten Event geloggt → Pattern: Kaffee vor Meetings"
|
|
|
|
2. **LLM-basiert (Tier 1 browser):** Woechentlich zusammengefasste Events an lokales Gemma-Modell:
|
|
- "Hier sind die Events der letzten Woche. Welche Muster und Praeferenzen erkennst du?"
|
|
- Ergebnis als JSON parsen → MemoryFact[] schreiben
|
|
|
|
### 6.3 Confidence-Lifecycle
|
|
|
|
```
|
|
Neuer Fakt erkannt → confidence: 0.3
|
|
Nochmal bestaetigt → confidence: 0.5
|
|
3+ Bestaetigungen → confidence: 0.7
|
|
10+ Bestaetigungen → confidence: 0.9
|
|
Widerspruch erkannt → confidence -= 0.15
|
|
Laenger als 30 Tage nicht → confidence -= 0.05/Woche
|
|
bestaetigt
|
|
confidence < 0.1 → Fakt wird geloescht
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Context Document Generator
|
|
|
|
### 7.1 Zweck
|
|
|
|
Komprimiert den gesamten Nutzerzustand in ein ~500 Token Dokument, das als System-Prompt an das LLM geht. Aktualisiert sich bei jedem Companion-Aufruf.
|
|
|
|
### 7.2 Template
|
|
|
|
```typescript
|
|
// projections/context-document.ts
|
|
|
|
export function generateContextDocument(
|
|
day: DaySnapshot,
|
|
streaks: StreakInfo[],
|
|
goals: LocalGoal[],
|
|
memory: MemoryFact[],
|
|
correlations: Correlation[]
|
|
): string {
|
|
return `## Nutzer-Kontext (${day.date})
|
|
|
|
### Heute
|
|
- ${day.tasks.dueToday.length} Tasks faellig (${day.tasks.completed} erledigt, ${day.tasks.overdue} ueberfaellig)
|
|
- ${day.events.total} Termine${day.events.nextEvent ? ` — naechster: ${day.events.nextEvent.title} um ${day.events.nextEvent.startTime}` : ''}
|
|
- Wasser: ${day.drinks.water.ml}ml von ${day.drinks.water.goal}ml (${day.drinks.water.percent}%)
|
|
- Ernaehrung: ${day.nutrition.calories.actual} von ${day.nutrition.calories.goal} kcal, ${day.nutrition.meals} Mahlzeiten
|
|
${day.places.tracking ? `- Standort-Tracking aktiv` : ''}
|
|
|
|
### Ziele
|
|
${goals.filter(g => g.status === 'active').map(g =>
|
|
`- "${g.title}" — ${g.currentValue}/${g.target.value} (${g.target.period})`
|
|
).join('\n')}
|
|
|
|
### Streaks
|
|
${streaks.filter(s => s.status !== 'broken').map(s =>
|
|
`- ${s.label}: ${s.currentStreak} Tage${s.status === 'at_risk' ? ' (GEFAEHRDET)' : ''}`
|
|
).join('\n')}
|
|
${streaks.filter(s => s.status === 'broken').map(s =>
|
|
`- ${s.label}: UNTERBROCHEN seit ${daysSince(s.lastActiveDate)} Tagen`
|
|
).join('\n')}
|
|
|
|
### Bekannte Muster
|
|
${memory.filter(m => m.confidence > 0.5).map(m => `- ${m.content}`).join('\n')}
|
|
|
|
### Korrelationen
|
|
${correlations.slice(0, 3).map(c => `- ${c.sentence}`).join('\n')}
|
|
`;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Tool Layer (LLM Write-Access)
|
|
|
|
### 8.1 ModuleTool Interface
|
|
|
|
**Neues File: `apps/mana/apps/web/src/lib/data/tools/types.ts`**
|
|
|
|
```typescript
|
|
export interface ModuleTool {
|
|
name: string; // 'create_task', 'log_drink'
|
|
module: string; // 'todo', 'drink'
|
|
description: string; // Fuer LLM Function-Schema
|
|
parameters: ToolParameter[];
|
|
execute: (params: Record<string, unknown>) => Promise<ToolResult>;
|
|
}
|
|
|
|
export interface ToolParameter {
|
|
name: string;
|
|
type: 'string' | 'number' | 'boolean';
|
|
description: string;
|
|
required: boolean;
|
|
enum?: string[]; // z.B. ['water', 'coffee', 'tea']
|
|
}
|
|
|
|
export interface ToolResult {
|
|
success: boolean;
|
|
data?: unknown;
|
|
message?: string; // Menschenlesbare Bestaetigung
|
|
}
|
|
```
|
|
|
|
### 8.2 Tool-Definitionen (5 Pilot-Module)
|
|
|
|
**Jedes Modul bekommt eine `tools.ts`:**
|
|
|
|
```typescript
|
|
// modules/todo/tools.ts
|
|
export const todoTools: ModuleTool[] = [
|
|
{
|
|
name: 'create_task',
|
|
module: 'todo',
|
|
description: 'Erstellt einen neuen Task',
|
|
parameters: [
|
|
{ name: 'title', type: 'string', description: 'Titel des Tasks', required: true },
|
|
{ name: 'dueDate', type: 'string', description: 'Faelligkeitsdatum (YYYY-MM-DD)', required: false },
|
|
{ name: 'priority', type: 'number', description: 'Prioritaet 0-3', required: false },
|
|
],
|
|
execute: async (params) => {
|
|
const task = await tasksStore.createTask({
|
|
title: params.title as string,
|
|
dueDate: params.dueDate as string | undefined,
|
|
priority: params.priority as number | undefined,
|
|
});
|
|
return { success: true, data: task, message: `Task "${task.title}" erstellt` };
|
|
},
|
|
},
|
|
{
|
|
name: 'complete_task',
|
|
module: 'todo',
|
|
description: 'Markiert einen Task als erledigt',
|
|
parameters: [
|
|
{ name: 'taskId', type: 'string', description: 'ID des Tasks', required: true },
|
|
],
|
|
execute: async (params) => {
|
|
await tasksStore.completeTask(params.taskId as string);
|
|
return { success: true, message: 'Task erledigt' };
|
|
},
|
|
},
|
|
];
|
|
|
|
// modules/drink/tools.ts
|
|
export const drinkTools: ModuleTool[] = [
|
|
{
|
|
name: 'log_drink',
|
|
module: 'drink',
|
|
description: 'Loggt ein Getraenk',
|
|
parameters: [
|
|
{ name: 'drinkType', type: 'string', description: 'Art', required: true, enum: ['water', 'coffee', 'tea', 'juice', 'alcohol', 'other'] },
|
|
{ name: 'quantityMl', type: 'number', description: 'Menge in ml', required: true },
|
|
{ name: 'name', type: 'string', description: 'Name des Getraenks', required: false },
|
|
],
|
|
execute: async (params) => {
|
|
const entry = await drinkStore.logDrink({
|
|
name: (params.name as string) ?? (params.drinkType as string),
|
|
drinkType: params.drinkType as DrinkType,
|
|
quantityMl: params.quantityMl as number,
|
|
});
|
|
return { success: true, data: entry, message: `${params.quantityMl}ml ${params.drinkType} geloggt` };
|
|
},
|
|
},
|
|
];
|
|
|
|
// modules/calendar/tools.ts — create_event
|
|
// modules/nutriphi/tools.ts — log_meal
|
|
// modules/places/tools.ts — record_visit, create_place
|
|
```
|
|
|
|
### 8.3 Tool Registry
|
|
|
|
**Neues File: `apps/mana/apps/web/src/lib/data/tools/registry.ts`**
|
|
|
|
```typescript
|
|
import { todoTools } from '$lib/modules/todo/tools';
|
|
import { calendarTools } from '$lib/modules/calendar/tools';
|
|
import { drinkTools } from '$lib/modules/drink/tools';
|
|
import { nutriphiTools } from '$lib/modules/nutriphi/tools';
|
|
import { placesTools } from '$lib/modules/places/tools';
|
|
|
|
const ALL_TOOLS: ModuleTool[] = [
|
|
...todoTools,
|
|
...calendarTools,
|
|
...drinkTools,
|
|
...nutriphiTools,
|
|
...placesTools,
|
|
];
|
|
|
|
export function getTools(): ModuleTool[] {
|
|
return ALL_TOOLS;
|
|
}
|
|
|
|
export function getTool(name: string): ModuleTool | undefined {
|
|
return ALL_TOOLS.find((t) => t.name === name);
|
|
}
|
|
|
|
export function getToolsForLlm(): LlmFunctionSchema[] {
|
|
return ALL_TOOLS.map((t) => ({
|
|
name: t.name,
|
|
description: t.description,
|
|
parameters: {
|
|
type: 'object',
|
|
properties: Object.fromEntries(
|
|
t.parameters.map((p) => [p.name, {
|
|
type: p.type,
|
|
description: p.description,
|
|
...(p.enum ? { enum: p.enum } : {}),
|
|
}])
|
|
),
|
|
required: t.parameters.filter((p) => p.required).map((p) => p.name),
|
|
},
|
|
}));
|
|
}
|
|
```
|
|
|
|
### 8.4 Integration mit LLM Orchestrator
|
|
|
|
Der bestehende `LlmOrchestrator` in `@mana/shared-llm` bekommt eine neue Methode:
|
|
|
|
```typescript
|
|
// In shared-llm/src/orchestrator.ts
|
|
|
|
async runWithTools<TOut>(
|
|
task: LlmTask,
|
|
input: { messages: ChatMessage[]; tools: LlmFunctionSchema[] },
|
|
): Promise<LlmTaskResult<TOut>>
|
|
```
|
|
|
|
Das LLM gibt `tool_use` Responses zurueck, die der Orchestrator ueber `getTool(name).execute(params)` ausfuehrt. Das Ergebnis wird als `tool_result` Message zurueckgefuettert.
|
|
|
|
---
|
|
|
|
## 9. Rule Engine (Pulse)
|
|
|
|
### 9.1 Prinzip
|
|
|
|
Die Rule Engine liest Projections und erzeugt Nudges. Kein LLM — rein deterministisch. Laeuft auf zwei Wegen:
|
|
|
|
1. **Event-getriggert:** Reagiert auf Domain Events (z.B. `TaskCompleted` → Streak-Check)
|
|
2. **Zeitgesteuert:** Periodische Checks (Morgen-Summary, Abend-Reflexion, stuendlich)
|
|
|
|
### 9.2 Rule Interface
|
|
|
|
**Neues File: `apps/mana/apps/web/src/lib/companion/rules/types.ts`**
|
|
|
|
```typescript
|
|
export interface PulseRule {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
|
|
// Trigger
|
|
trigger:
|
|
| { kind: 'event'; eventType: string }
|
|
| { kind: 'schedule'; cron: string } // z.B. '0 8 * * *' (08:00 taeglich)
|
|
| { kind: 'interval'; minutes: number }; // z.B. 60 (stuendlich)
|
|
|
|
// Check — gibt null zurueck wenn kein Nudge noetig
|
|
check: (ctx: RuleContext) => Promise<Nudge | null>;
|
|
}
|
|
|
|
export interface RuleContext {
|
|
day: DaySnapshot;
|
|
streaks: StreakInfo[];
|
|
goals: LocalGoal[];
|
|
memory: MemoryFact[];
|
|
now: Date;
|
|
}
|
|
|
|
export interface Nudge {
|
|
id: string;
|
|
type: NudgeType;
|
|
title: string;
|
|
body: string;
|
|
priority: 'low' | 'medium' | 'high';
|
|
actionLabel?: string; // "Jetzt loggen"
|
|
actionRoute?: string; // '/drink'
|
|
actionTool?: string; // 'log_drink' — Companion kann direkt ausfuehren
|
|
expiresAt?: string; // wann der Nudge irrelevant wird
|
|
}
|
|
|
|
type NudgeType =
|
|
| 'streak_warning'
|
|
| 'goal_progress'
|
|
| 'goal_reached'
|
|
| 'morning_summary'
|
|
| 'evening_reflection'
|
|
| 'overdue_tasks'
|
|
| 'water_reminder'
|
|
| 'meal_reminder'
|
|
| 'correlation_insight';
|
|
```
|
|
|
|
### 9.3 Vordefinierte Rules (Pilot)
|
|
|
|
```typescript
|
|
// rules/water-reminder.ts
|
|
export const waterReminderRule: PulseRule = {
|
|
id: 'water-reminder',
|
|
name: 'Wasser-Erinnerung',
|
|
trigger: { kind: 'interval', minutes: 90 },
|
|
async check(ctx) {
|
|
const { water } = ctx.day.drinks;
|
|
if (water.percent >= 100) return null; // Ziel erreicht
|
|
const hourOfDay = ctx.now.getHours();
|
|
if (hourOfDay < 8 || hourOfDay > 21) return null; // Nachtruhe
|
|
|
|
const remaining = water.goal - water.ml;
|
|
const hoursLeft = 21 - hourOfDay;
|
|
const mlPerHour = Math.ceil(remaining / hoursLeft);
|
|
|
|
return {
|
|
id: `water-${ctx.day.date}-${hourOfDay}`,
|
|
type: 'water_reminder',
|
|
title: 'Wasser trinken',
|
|
body: `Noch ${remaining}ml bis zum Ziel. ~${mlPerHour}ml pro Stunde.`,
|
|
priority: water.percent < 50 ? 'medium' : 'low',
|
|
actionLabel: 'Glas loggen',
|
|
actionTool: 'log_drink',
|
|
};
|
|
},
|
|
};
|
|
|
|
// rules/streak-warning.ts
|
|
export const streakWarningRule: PulseRule = {
|
|
id: 'streak-warning',
|
|
name: 'Streak-Warnung',
|
|
trigger: { kind: 'schedule', cron: '0 18 * * *' }, // 18:00 taeglich
|
|
async check(ctx) {
|
|
const atRisk = ctx.streaks.filter(s => s.status === 'at_risk');
|
|
if (atRisk.length === 0) return null;
|
|
|
|
const best = atRisk.reduce((a, b) => a.currentStreak > b.currentStreak ? a : b);
|
|
return {
|
|
id: `streak-${ctx.day.date}`,
|
|
type: 'streak_warning',
|
|
title: `${best.label}-Streak in Gefahr!`,
|
|
body: `${best.currentStreak} Tage — nicht heute brechen.`,
|
|
priority: best.currentStreak > 7 ? 'high' : 'medium',
|
|
};
|
|
},
|
|
};
|
|
|
|
// rules/morning-summary.ts
|
|
// rules/evening-reflection.ts
|
|
// rules/overdue-tasks.ts
|
|
// rules/meal-reminder.ts
|
|
// rules/goal-reached.ts
|
|
```
|
|
|
|
### 9.4 Rule Engine Runner
|
|
|
|
Integriert sich in den bestehenden Reminder-Scheduler als zusaetzliche Source:
|
|
|
|
```typescript
|
|
// companion/rules/engine.ts
|
|
|
|
export function createRuleEngine(rules: PulseRule[]): ReminderSource {
|
|
return {
|
|
id: 'companion-pulse',
|
|
async checkDue(): Promise<DueReminder[]> {
|
|
const ctx = await buildRuleContext();
|
|
const nudges: Nudge[] = [];
|
|
|
|
for (const rule of rules) {
|
|
if (shouldTrigger(rule)) {
|
|
const nudge = await rule.check(ctx);
|
|
if (nudge && !isDismissed(nudge.id)) {
|
|
nudges.push(nudge);
|
|
}
|
|
}
|
|
}
|
|
|
|
return nudges.map(toReminder);
|
|
},
|
|
async markSent(id) { /* track in localStorage */ },
|
|
};
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Feedback Loop
|
|
|
|
### 10.1 Datenmodell
|
|
|
|
**Neue Dexie-Tabelle: `_nudgeOutcomes`**
|
|
```
|
|
_nudgeOutcomes: '++id, nudgeId, nudgeType, outcome, timestamp, [nudgeType+outcome]'
|
|
```
|
|
|
|
```typescript
|
|
export interface NudgeOutcome {
|
|
id?: number;
|
|
nudgeId: string;
|
|
nudgeType: NudgeType;
|
|
outcome: 'acted' | 'dismissed' | 'snoozed' | 'expired' | 'auto_resolved';
|
|
latencyMs?: number; // Zeit bis Reaktion
|
|
timestamp: string;
|
|
}
|
|
```
|
|
|
|
### 10.2 Lerneffekt
|
|
|
|
Aggregation ueber `_nudgeOutcomes` beeinflusst die Rule Engine:
|
|
|
|
```typescript
|
|
// Beispiel: Wasser-Reminder wird um 10:00 immer dismissed
|
|
// → confidence fuer "Nutzer mag morgens keine Wasser-Reminder" steigt
|
|
// → Rule Engine verschiebt auf 11:00
|
|
|
|
// Beispiel: Streak-Warning um 18:00 fuehrt zu 80% zu Aktion
|
|
// → confidence fuer "18:00 ist guter Zeitpunkt" steigt
|
|
// → bleibt bei 18:00
|
|
```
|
|
|
|
Memory-Facts werden aus Outcome-Patterns extrahiert und fliessen in den Context Document Generator.
|
|
|
|
---
|
|
|
|
## 11. Companion Chat (Interaction Layer)
|
|
|
|
### 11.1 Modul-Struktur
|
|
|
|
**Neues Modul: `apps/mana/apps/web/src/lib/modules/companion/`**
|
|
|
|
```
|
|
companion/
|
|
module.config.ts — Registriert companion-Tabellen
|
|
collections.ts — conversations, messages, rituals, goals
|
|
stores/
|
|
chat.svelte.ts — Chat-Mutations (send, receive, tool-call)
|
|
rituals.svelte.ts — Ritual-CRUD + Step-Execution
|
|
goals.svelte.ts — Goal-CRUD + Progress-Tracking
|
|
queries.ts — Live-Queries fuer Chat, Rituals, Goals
|
|
tools.ts — Companion-eigene Tools (read_context, get_insights)
|
|
components/
|
|
CompanionChat.svelte — Chat-Interface mit Tool-Execution
|
|
CompanionFeed.svelte — Timeline von Nudges + Insights + Chat
|
|
RitualRunner.svelte — Step-by-Step Ritual-UI
|
|
GoalCard.svelte — Ziel-Fortschritts-Anzeige
|
|
```
|
|
|
|
### 11.2 Chat-Flow
|
|
|
|
```
|
|
User: "Wie laeuft mein Tag?"
|
|
|
|
|
v
|
|
CompanionChat → LLM Orchestrator
|
|
|
|
|
| System Prompt = Context Document (~500 Tokens)
|
|
| + Tool Schemas (getToolsForLlm())
|
|
| + User Message
|
|
|
|
|
v
|
|
LLM (Gemma lokal ODER Gemini Cloud)
|
|
|
|
|
| Response: "Du hast heute 3/7 Tasks erledigt und erst 400ml
|
|
| Wasser getrunken. Dein Kalender ist ab 15:00 frei — guter
|
|
| Zeitpunkt fuer die ueberfaelligen Tasks. Soll ich dich
|
|
| in 2 Stunden ans Wasser erinnern?"
|
|
|
|
|
| tool_use: create_reminder(...)
|
|
|
|
|
v
|
|
Tool Execution → drinkStore / remindersStore
|
|
|
|
|
v
|
|
CompanionChat zeigt Antwort + Aktions-Bestaetigung
|
|
```
|
|
|
|
### 11.3 Ritual-Generierung
|
|
|
|
```
|
|
User: "Erstell mir eine Morgenroutine"
|
|
|
|
|
v
|
|
LLM + Context Document + Tool Schemas
|
|
|
|
|
| LLM sieht: Nutzer hat Drink, Todo, Nutriphi, Calendar aktiv
|
|
| Memory: "Trinkt morgens zuerst Kaffee"
|
|
| Goals: "8 Glaeser Wasser/Tag"
|
|
|
|
|
v
|
|
Generiertes Ritual:
|
|
1. Glas Wasser loggen (tool: log_drink, water, 250ml)
|
|
2. Stimmung checken (free_text → journal)
|
|
3. Tages-Tasks priorisieren (zeigt DaySnapshot.tasks.dueToday)
|
|
4. Kalender-Ueberblick (zeigt DaySnapshot.events.upcoming)
|
|
5. Fruehstueck loggen (tool: log_meal)
|
|
```
|
|
|
|
---
|
|
|
|
## 12. Dateien & Ordnerstruktur
|
|
|
|
✅ = implementiert, ⬜ = ausstehend
|
|
|
|
```
|
|
apps/mana/apps/web/src/lib/
|
|
data/
|
|
events/ ✅ Phase 1
|
|
event-bus.ts ✅ EventBus Singleton (sync dispatch, microtask handlers)
|
|
event-store.ts ✅ Persistenz in _events Tabelle (90d TTL, 50k max)
|
|
emit.ts ✅ emitDomainEvent() Helper
|
|
types.ts ✅ DomainEvent, EventMeta, EventBus Interfaces
|
|
catalog.ts ✅ 22 Event-Typen (ManaEvent union type)
|
|
index.ts ✅ Barrel Export
|
|
projections/ ✅ Phase 2
|
|
day-snapshot.ts ✅ useDaySnapshot() — live Tagesaggregation
|
|
streaks.ts ✅ useStreaks() — 3 Streak-Typen, 90d Lookback
|
|
context-document.ts ✅ generateContextDocument() — ~500 Token LLM-Prompt
|
|
correlations.ts ✅ Phase 7 — Pearson ueber 7 Metriken
|
|
types.ts ✅ DaySnapshot, StreakInfo, TaskSummary, EventSummary
|
|
index.ts ✅ Barrel Export
|
|
tools/ ✅ Phase 4
|
|
types.ts ✅ ModuleTool, ToolParameter, ToolResult, LlmFunctionSchema
|
|
registry.ts ✅ registerTools(), getToolsForLlm()
|
|
executor.ts ✅ executeTool() mit Validierung + Typ-Coercion
|
|
init.ts ✅ initTools() — registriert alle 5 Module
|
|
index.ts ✅ Barrel Export
|
|
companion/
|
|
goals/ ✅ Phase 3
|
|
types.ts ✅ LocalGoal, GoalMetric, GoalTarget, 6 Templates
|
|
store.ts ✅ CRUD + Event-Bus-Subscription fuer Progress
|
|
queries.ts ✅ useActiveGoals(), useAllGoals()
|
|
index.ts ✅ Barrel Export
|
|
rules/ ✅ Phase 3
|
|
types.ts ✅ PulseRule, Nudge, NudgeType, RuleContext
|
|
rules.ts ✅ 5 Rules (water, streak, morning, overdue, meal)
|
|
engine.ts ✅ evaluateRules(), createPulseReminderSource()
|
|
index.ts ✅ Barrel Export
|
|
feedback/ ✅ Phase 3
|
|
types.ts ✅ NudgeOutcome
|
|
tracker.ts ✅ recordOutcome(), getOutcomeStats(), getActionRate()
|
|
index.ts ✅ Barrel Export
|
|
memory/ ✅ Phase 7
|
|
types.ts ✅ MemoryFact, Correlation
|
|
store.ts ✅ recordFact, contradictFact, applyDecay, getFacts
|
|
extractors.ts ✅ 3 Extractors (day-of-week, time-of-day, frequency)
|
|
index.ts ✅ Barrel Export
|
|
rituals/ ✅ Phase 6
|
|
types.ts ✅ 6 Step-Typen, 3 Templates
|
|
store.ts ✅ createFromTemplate, CRUD, logs
|
|
queries.ts ✅ useActiveRituals, useAllRituals
|
|
index.ts ✅ Barrel Export
|
|
modules/
|
|
companion/ ✅ Phase 5
|
|
types.ts ✅ LocalConversation, LocalMessage
|
|
collections.ts ✅ companionConversations, companionMessages
|
|
stores/chat.svelte.ts ✅ Conversation + Message CRUD
|
|
queries.ts ✅ useConversations, useMessages
|
|
engine.ts ✅ runCompanionChat (LLM + Tools + Context)
|
|
index.ts ✅ Barrel Export
|
|
components/
|
|
CompanionChat.svelte ✅ Chat-UI mit Streaming + Tool-Results
|
|
RitualRunner.svelte ✅ Step-by-Step Runner
|
|
CompanionFeed.svelte ⬜ Timeline (spaetere Iteration)
|
|
GoalCard.svelte ⬜ Goal-Fortschritts-Widget (spaetere Iteration)
|
|
todo/
|
|
tools.ts ✅ 3 Tools (create, complete, stats)
|
|
stores/tasks.svelte.ts ✅ 5 Events (Created, Completed, Uncompleted, Deleted, Subtasks)
|
|
calendar/
|
|
tools.ts ✅ 2 Tools (create_event, get_todays_events)
|
|
stores/events.svelte.ts ✅ 3 Events (Created, Updated, Deleted)
|
|
drink/
|
|
tools.ts ✅ 3 Tools (log, progress, undo)
|
|
stores/drink.svelte.ts ✅ 3 Events (Logged, Deleted, Undone)
|
|
nutriphi/
|
|
tools.ts ✅ 2 Tools (log_meal, nutrition_summary)
|
|
mutations.ts ✅ 3 Events (Logged, PhotoLogged, Deleted)
|
|
places/
|
|
tools.ts ✅ 4 Tools (create, visit, get_places, location)
|
|
stores/places.svelte.ts ✅ 3 Events (Created, Deleted, Visited)
|
|
stores/tracking.svelte.ts ✅ 3 Events (Started, Stopped, LocationLogged)
|
|
```
|
|
|
|
---
|
|
|
|
## 13. Dexie-Tabellen
|
|
|
|
### Implementiert (v10 Schema)
|
|
|
|
```typescript
|
|
// Event Store — append-only domain event log
|
|
_events: '++seq, type, meta.appId, meta.timestamp, meta.recordId, [meta.appId+meta.timestamp], [type+meta.timestamp]',
|
|
|
|
// Goals — companion brain goal tracking
|
|
companionGoals: 'id, moduleId, status, [moduleId+status]',
|
|
|
|
// Semantic Memory — extracted user patterns (prepared, not yet populated)
|
|
_memory: 'id, category, confidence, lastConfirmed, [category+confidence]',
|
|
|
|
// Feedback Loop — nudge outcome tracking
|
|
_nudgeOutcomes: '++id, nudgeId, nudgeType, outcome, timestamp, [nudgeType+outcome]',
|
|
```
|
|
|
|
### Noch ausstehend (Phase 5+)
|
|
|
|
```typescript
|
|
// Companion Chat (Phase 5)
|
|
companionConversations: 'id, createdAt',
|
|
companionMessages: 'id, conversationId, role, createdAt, [conversationId+createdAt]',
|
|
|
|
// Rituals (Phase 6)
|
|
rituals: 'id, status, createdAt',
|
|
ritualSteps: 'id, ritualId, order, [ritualId+order]',
|
|
ritualLogs: '++id, ritualId, date, [ritualId+date]',
|
|
```
|
|
|
|
---
|
|
|
|
## 14. Implementierungs-Reihenfolge
|
|
|
|
### Phase 1: Event-Fundament — ERLEDIGT (2026-04-13)
|
|
|
|
Commit: `e927c1f10`
|
|
|
|
1. ✅ `data/events/` — EventBus, EventStore, emit Helper, Type Catalog
|
|
2. ✅ Domain Events fuer 5 Pilot-Module definiert (catalog.ts, 22 Event-Typen)
|
|
3. ✅ Stores der 5 Module umgebaut: `emit()` Calls eingefuegt
|
|
4. ✅ Event Store Subscriber: `eventBus.onAny()` → `_events` Tabelle (v10 Schema)
|
|
5. ⬜ Tests: noch ausstehend
|
|
|
|
**Ergebnis:** Semantischer Event-Stream fliesst. 20 Domain Events aus 5 Modulen.
|
|
|
|
**Implementierungsnotizen:**
|
|
- Events werden im Store emittiert (nicht im Dexie-Hook) — der Store kennt die Semantik
|
|
- `emitDomainEvent()` Helper reduziert Boilerplate auf eine Zeile pro Event
|
|
- Re-Entrancy-Guard im EventBus verhindert Endlos-Loops
|
|
- `_activity` Tabelle bleibt parallel bestehen (Sync-Debugging)
|
|
|
|
### Phase 2: Projections — ERLEDIGT (2026-04-13)
|
|
|
|
Commit: `40e1145e9`
|
|
|
|
1. ✅ DaySnapshot Projection (live Dexie-Queries ueber alle 5 Module)
|
|
2. ✅ Streaks Projection (3 Streak-Definitionen: Wasser, Tasks, Mahlzeiten, 90-Tage Lookback)
|
|
3. ✅ Context Document Generator (Template-basiert, ~300-500 Token)
|
|
4. ⬜ Dashboard-Widget: "Mein Tag" Karte — spaeter in UI-Phase
|
|
|
|
**Ergebnis:** Zentraler Ueberblick ueber alle 5 Module, live-reaktiv.
|
|
|
|
**Implementierungsnotizen:**
|
|
- Projections nutzen `useLiveQueryWithDefault` aus `@mana/local-store/svelte`
|
|
- DaySnapshot queried 5 Dexie-Tabellen + decrypted in einem buildSnapshot()-Call
|
|
- Streaks berechnen per checkDate() ob ein Tag "zaehlt" (z.B. Wasser-Ziel erreicht)
|
|
- Context Document ist reines String-Template, kein LLM noetig
|
|
- `startEventStore()` in `(app)/+layout.svelte` bei Auth-Ready gewired
|
|
|
|
### Phase 3: Goals + Pulse — ERLEDIGT (2026-04-13)
|
|
|
|
Commit: `9066b6c9a`
|
|
|
|
1. ✅ Goal Datenmodell + Store + Queries (`companion/goals/`)
|
|
2. ✅ Goal-Tracking via Event-Bus-Subscription (auto-increment currentValue)
|
|
3. ✅ 6 Goal-Templates (Wasser, Tasks, Mahlzeiten, Kalorien, Orte, Kaffee-Limit)
|
|
4. ✅ Rule Engine mit 5 Rules (`companion/rules/`)
|
|
5. ✅ ReminderSource-Adapter fuer bestehenden Scheduler
|
|
6. ⬜ Nudge-UI: Toast / Bottom-Sheet — in Phase 5 (Companion Chat)
|
|
|
|
**Ergebnis:** Goals tracken automatisch, Rules erzeugen Nudges.
|
|
|
|
**Implementierungsnotizen:**
|
|
- Goals leben in `companionGoals` Tabelle (v10 Schema), nicht im Core-Modul
|
|
- Goal-Tracker subscribed auf `eventBus.onAny()` und matched per eventType + Filter
|
|
- Perioden-Reset (day/week/month) passiert automatisch beim naechsten Event
|
|
- `GoalReached` Event wird emittiert wenn Ziel erstmals in einer Periode erreicht
|
|
- Rules nutzen localStorage fuer Dismissal-Tracking und Last-Run-Timestamps
|
|
- `_memory` und `_nudgeOutcomes` Tabellen vorbereitet (v10 Schema)
|
|
|
|
### Phase 4: Tool Layer — ERLEDIGT (2026-04-13)
|
|
|
|
Commit: `66dd684bb`
|
|
|
|
1. ✅ ModuleTool Interface + Registry (dynamische Registrierung)
|
|
2. ✅ tools.ts fuer 5 Pilot-Module (13 Tools total)
|
|
3. ✅ Tool Executor mit Parameter-Validierung + Typ-Coercion
|
|
4. ✅ LLM Function Schema Generator (`getToolsForLlm()`)
|
|
5. ⬜ Integration in LLM Orchestrator (`runWithTools`) — in Phase 5
|
|
|
|
**Ergebnis:** 13 Tools bereit fuer LLM Function-Calling.
|
|
|
|
**Implementierungsnotizen:**
|
|
- Registry nutzt `registerTools()` Pattern statt statische Imports (tree-shaking-freundlich)
|
|
- `initTools()` in `(app)/+layout.svelte` gewired neben `startEventStore()`
|
|
- Executor coerced String→Number und String→Boolean automatisch
|
|
- Tools pro Modul: Todo (3), Calendar (2), Drink (3), Nutriphi (2), Places (4)
|
|
- Jeder Tool hat eine `message` Feld fuer menschenlesbare Bestaetigung
|
|
|
|
### Phase 5: Companion Chat — ERLEDIGT (2026-04-13)
|
|
|
|
Commit: `46db527f8`
|
|
|
|
1. ✅ Companion Modul (types, collections, stores/chat, queries)
|
|
2. ✅ CompanionChat Svelte-Komponente (Streaming, Tool-Results inline)
|
|
3. ✅ Chat-Flow: Context Document als System-Prompt + Tool Schemas + LLM
|
|
4. ✅ `/companion` Route mit Sidebar (Gespraechsliste) + Chat-Area
|
|
5. ⬜ CompanionFeed: Timeline von Nudges + Chat — spaetere UI-Iteration
|
|
|
|
**Ergebnis:** Nutzer kann mit dem System sprechen und Aktionen ausfuehren.
|
|
|
|
**Implementierungsnotizen:**
|
|
- Chat nutzt `@mana/local-llm` (Gemma, browser-lokal) direkt — kein Server-Call
|
|
- Tool Calling via JSON-Block-Extraction (`\`\`\`tool {...}\`\`\``) statt nativem Function-Calling (Gemma unterstuetzt das nicht nativ)
|
|
- Max 3 Tool-Call-Runden pro Nachricht (verhindert Endlos-Loops)
|
|
- Conversations + Messages persistent in IndexedDB (`companionConversations`, `companionMessages`)
|
|
- Entscheidung: Companion lebt als eigenes Modul unter `/companion`, nicht als Overlay
|
|
- Streaming via `onToken` Callback — erster Round streamt, Folgerunden (nach Tool-Call) nicht
|
|
|
|
### Phase 6: Rituale — ERLEDIGT (2026-04-13)
|
|
|
|
Commit: `41357b254`
|
|
|
|
1. ✅ Ritual Datenmodell (6 Step-Typen: tool_call, number_input, text_input, mood_picker, info_display, checklist)
|
|
2. ✅ RitualRunner Komponente (Step-Card-UI, Progress-Bar, Tool-Execution)
|
|
3. ⬜ AI-Ritual-Generierung via Companion Chat — spaetere Iteration
|
|
4. ✅ 3 Ritual-Templates (Morgenroutine, Abendroutine, Trink-Check)
|
|
5. ✅ `/companion/rituals` Route mit Template-Picker + Ritual-Liste
|
|
|
|
**Ergebnis:** Gefuehrte Routinen die in Module schreiben.
|
|
|
|
**Implementierungsnotizen:**
|
|
- Rituale leben in `companion/rituals/` (nicht als eigenes Modul)
|
|
- Steps referenzieren Tools per Name — dieselben Tools die der Chat nutzt
|
|
- `info_display` Steps zeigen Projektionsdaten (Tasks, Events, Drinks, Nutrition, Streaks)
|
|
- Completion-Logs tracken wieviele Steps pro Tag abgeschlossen wurden
|
|
- Templates sind statisch definiert — AI-Generierung wird in Phase 5 integriert
|
|
|
|
### Phase 7: Memory + Correlations — ERLEDIGT (2026-04-13)
|
|
|
|
Commit: `87a1dd682`
|
|
|
|
1. ✅ Semantic Memory Store (`_memory` Tabelle, Confidence-Lifecycle)
|
|
2. ✅ 3 regelbasierte Pattern-Extractors (11 Extraction-Rules ueber 5 Module)
|
|
3. ✅ Correlation Engine (Pearson ueber 7 Metriken, cross-modul)
|
|
4. ✅ Memory + Correlations in Context Document integriert
|
|
5. ✅ Feedback Loop: `_nudgeOutcomes` Tabelle + Tracker (Phase 3)
|
|
6. ⬜ LLM-basierte Memory-Extraktion — spaetere Iteration
|
|
|
|
**Ergebnis:** System lernt Muster, findet Korrelationen, alles fliesst ins LLM.
|
|
|
|
**Implementierungsnotizen:**
|
|
- Pattern Extractors: day-of-week (Wochentags-Muster), time-of-day (4h-Peak-Fenster), frequency (Tages-Durchschnitt)
|
|
- Confidence: 0.3 initial, +0.15 pro Bestaetigung, -0.15 bei Widerspruch, Decay nach 30 Tagen
|
|
- Correlations nur cross-modul (gleich-Modul wird uebersprungen, trivial korreliert)
|
|
- Nur Korrelationen mit |r| >= 0.3 und >= 14 Tage Daten werden behalten
|
|
- `extractAllPatterns()` soll taeglich laufen (manuell oder via Scheduled Rule)
|
|
- `computeCorrelations()` berechnet on-demand, nicht persistent gecached
|
|
|
|
### Phase 8: Rollout auf weitere Module (Woche 8+)
|
|
|
|
Pro Modul:
|
|
1. Domain Events definieren (catalog.ts erweitern)
|
|
2. Store Mutations mit emit() versehen
|
|
3. tools.ts schreiben
|
|
4. Projections erweitern (DaySnapshot Felder)
|
|
5. Goal-Templates hinzufuegen
|
|
6. Pulse Rules hinzufuegen
|
|
|
|
Geschaetzter Aufwand pro Modul: 1-2 Tage.
|
|
|
|
---
|
|
|
|
## 15. Abhaengigkeiten & Reihenfolge-Graph
|
|
|
|
```
|
|
Phase 1 (Events) ──────┬──> Phase 2 (Projections)
|
|
| |
|
|
| v
|
|
├──> Phase 3 (Goals + Pulse)
|
|
| |
|
|
v v
|
|
Phase 4 (Tools) ──> Phase 5 (Companion Chat)
|
|
|
|
|
v
|
|
Phase 6 (Rituale)
|
|
|
|
|
v
|
|
Phase 7 (Memory)
|
|
|
|
|
v
|
|
Phase 8 (Rollout)
|
|
```
|
|
|
|
**Status: Phase 1-7 ERLEDIGT (2026-04-13).** Phase 8 (Rollout) ist der naechste Schritt.
|
|
|
|
---
|
|
|
|
## 16. Privacy-Garantien
|
|
|
|
| Daten | Verarbeitung | Speicherung |
|
|
|-------|-------------|-------------|
|
|
| Domain Events | Lokal (Browser) | IndexedDB, encrypted |
|
|
| Projections | Lokal (Browser) | In-Memory, nicht persistiert |
|
|
| Goals | Lokal + Sync | IndexedDB → mana-sync (encrypted) |
|
|
| Memory Facts | Lokal (Browser) | IndexedDB, encrypted |
|
|
| Context Document | Lokal (Browser) | In-Memory, nie persistiert |
|
|
| LLM Inference | Tier 1: Browser (Gemma) | Kein Server-Kontakt |
|
|
| | Tier 2: mana-llm (Ollama) | Context geht an eigenen Server |
|
|
| | Tier 3: Cloud (Gemini) | Nur mit explizitem Consent |
|
|
| Nudge Outcomes | Lokal (Browser) | IndexedDB, nicht synced |
|
|
| Tool Execution | Lokal (Browser) | Writes gehen in Module-Tabellen |
|
|
|
|
**Invariante:** Sensitive Daten (Journal, Dreams, Finance, Nutriphi) werden **nie** an Tier 2/3 gesendet — erzwungen durch `contentClass: 'sensitive'` im LLM Orchestrator.
|
|
|
|
---
|
|
|
|
## 17. Migration: _activity → _events
|
|
|
|
Die `_activity`-Tabelle bleibt vorerst bestehen (Sync-Debugging). Langfristig:
|
|
|
|
1. Phase 1-2: `_events` wird parallel zu `_activity` befuellt
|
|
2. Phase 7: Alle Consumers (Activity-Page, Debug-Tools) auf `_events` umstellen
|
|
3. Danach: `_activity`-Befuellung aus Hooks entfernen, Tabelle als deprecated markieren
|
|
4. Naechste Major-Version: Tabelle loeschen
|
|
|
|
---
|
|
|
|
## 18. Testing-Strategie
|
|
|
|
### Unit Tests
|
|
- Event Bus: emit/subscribe/unsubscribe, ordering, no re-entrant loops
|
|
- Projections: DaySnapshot korrekt aus Mock-Daten, Streak-Berechnung
|
|
- Rules: Nudge-Generierung unter verschiedenen Bedingungen
|
|
- Tools: Parameter-Validierung, Execute-Flow
|
|
- Correlations: Pearson-Berechnung, Signifikanz-Filter
|
|
|
|
### Integration Tests
|
|
- Store emit() → EventBus → EventStore → Projection Update
|
|
- Rule Engine → Nudge → UI → Outcome → Memory Update
|
|
- Companion Chat → LLM Mock → Tool Call → Store Mutation → Event
|
|
|
|
### E2E Tests
|
|
- Morgenroutine-Ritual durchspielen: 5 Steps → Daten in 3 Modulen
|
|
- Wasser-Ziel erreichen: 8x log_drink → GoalReached Event → Nudge
|
|
- Companion-Frage: "Wie war meine Woche?" → Context Document → Antwort
|
|
|
|
---
|
|
|
|
## 19. Manuelles Testen (Anleitung)
|
|
|
|
### Voraussetzungen
|
|
|
|
```bash
|
|
pnpm run mana:dev # Startet das Dev-Server auf :5173
|
|
```
|
|
|
|
Oeffne http://localhost:5173 im Browser (Chrome/Edge mit WebGPU-Support fuer LLM).
|
|
|
|
### 1. Event Bus verifizieren
|
|
|
|
Oeffne die Browser DevTools Console und teste:
|
|
|
|
```javascript
|
|
// Zugriff auf den Event Bus (global via Module-Import im App-Kontext)
|
|
// Am einfachsten: Daten erzeugen und in IndexedDB pruefen
|
|
|
|
// 1. Erstelle einen Task in der Todo-App
|
|
// 2. Oeffne DevTools → Application → IndexedDB → mana → _events
|
|
// 3. Dort sollte ein Event mit type="TaskCreated" erscheinen
|
|
|
|
// Alternativ via Console:
|
|
const db = await indexedDB.open('mana');
|
|
// Events sind in der _events Tabelle mit type, payload, meta
|
|
```
|
|
|
|
**Was zu pruefen ist:**
|
|
- Neuen Task erstellen → `TaskCreated` Event in `_events`
|
|
- Task erledigen → `TaskCompleted` Event
|
|
- Drink loggen → `DrinkLogged` Event
|
|
- Mahlzeit loggen → `MealLogged` Event
|
|
- Ort besuchen → `PlaceVisited` Event
|
|
|
|
### 2. Projections testen
|
|
|
|
Die Projections sind live-reaktiv. Am einfachsten via Browser Console:
|
|
|
|
```javascript
|
|
// DaySnapshot ist eine Svelte-reaktive Query.
|
|
// In einer Svelte-Komponente:
|
|
// import { useDaySnapshot } from '$lib/data/projections';
|
|
// const day = useDaySnapshot();
|
|
// console.log(day.value);
|
|
|
|
// Zum manuellen Testen: Daten erzeugen und schauen ob DaySnapshot reagiert
|
|
// 1. Gehe zu /drink und logge ein Glas Wasser
|
|
// 2. Die DaySnapshot.drinks.water.ml sollte sich erhoehen
|
|
// 3. Gehe zu /todo und erstelle+erledige einen Task
|
|
// 4. DaySnapshot.tasks.completed sollte steigen
|
|
```
|
|
|
|
### 3. Companion Chat testen
|
|
|
|
1. Navigiere zu `/companion`
|
|
2. Klicke "Gespraech starten"
|
|
3. **WICHTIG:** Der Chat nutzt `@mana/local-llm` (Gemma, ~500MB Download).
|
|
Beim ersten Mal muss das Modell heruntergeladen werden. Das dauert
|
|
je nach Verbindung 1-5 Minuten. WebGPU muss verfuegbar sein
|
|
(Chrome 113+, Edge 113+, kein Firefox/Safari).
|
|
4. Teste Nachrichten:
|
|
- "Wie sieht mein Tag aus?" → Sollte DaySnapshot-Daten zusammenfassen
|
|
- "Log mir ein Glas Wasser" → Sollte `log_drink` Tool aufrufen
|
|
- "Erstelle einen Task: Einkaufen gehen" → Sollte `create_task` Tool aufrufen
|
|
- "Wie viel Wasser habe ich heute getrunken?" → Nutzt Context Document
|
|
|
|
**Ohne WebGPU (Fallback):** Der Chat wird fehlschlagen wenn kein WebGPU
|
|
verfuegbar ist. In dem Fall die Engine temporaer auf einen Server-Endpoint
|
|
umbauen oder den Chat-Flow mit Mock-Responses testen.
|
|
|
|
### 4. Rituale testen
|
|
|
|
1. Navigiere zu `/companion/rituals`
|
|
2. Klicke "+ Neu" → waehle "Morgenroutine"
|
|
3. Klicke den Play-Button neben der erstellten Routine
|
|
4. Der RitualRunner zeigt Step fuer Step:
|
|
- Step 1: "Glas Wasser trinken" → Klick "Ausfuehren" → loggt 250ml Wasser
|
|
- Step 2: "Dein Tag auf einen Blick" → zeigt heutige Events
|
|
- Step 3: "Heutige Tasks" → zeigt faellige Tasks
|
|
- Step 4: "Deine Streaks" → zeigt Streak-Status
|
|
5. "Weiter" / "Fertig" navigiert durch die Steps
|
|
6. Pruefe in `/drink` ob das Wasser tatsaechlich geloggt wurde
|
|
|
|
### 5. Goals testen
|
|
|
|
Goals sind aktuell nur programmatisch testbar (kein UI). Via Console:
|
|
|
|
```javascript
|
|
// In einer Svelte-Komponente oder via Hot-Module:
|
|
import { goalStore, GOAL_TEMPLATES } from '$lib/companion/goals';
|
|
|
|
// Goal aus Template erstellen
|
|
const goal = await goalStore.createFromTemplate(GOAL_TEMPLATES[0]); // "8 Glaeser Wasser"
|
|
console.log(goal);
|
|
|
|
// Jetzt ein Wasser loggen → Goal currentValue sollte steigen
|
|
// (der Goal-Tracker subscribed auf den Event Bus)
|
|
```
|
|
|
|
### 6. Memory + Correlations testen
|
|
|
|
Braucht mindestens 7-14 Tage an Daten. Zum Testen mit Seed-Daten:
|
|
|
|
```javascript
|
|
// Pattern Extraction manuell ausfuehren:
|
|
import { extractAllPatterns } from '$lib/companion/memory';
|
|
await extractAllPatterns();
|
|
|
|
// Extrahierte Facts pruefen:
|
|
import { memoryStore } from '$lib/companion/memory';
|
|
const facts = await memoryStore.getFacts();
|
|
console.log(facts);
|
|
|
|
// Correlations berechnen:
|
|
import { computeCorrelations } from '$lib/data/projections';
|
|
const corrs = await computeCorrelations();
|
|
console.log(corrs);
|
|
```
|
|
|
|
**Hinweis:** Mit weniger als 7 Events pro Typ oder 14 Tagen Daten liefern
|
|
die Extractors und Correlations keine Ergebnisse — das ist beabsichtigt,
|
|
um Rauschen zu vermeiden.
|
|
|
|
### 7. Pulse Rules testen
|
|
|
|
Rules laufen ueber den Reminder-Scheduler (30s Intervall). Zum manuellen Test:
|
|
|
|
```javascript
|
|
import { evaluateRules } from '$lib/companion/rules';
|
|
import { useDaySnapshot } from '$lib/data/projections/day-snapshot';
|
|
import { useStreaks } from '$lib/data/projections/streaks';
|
|
|
|
// In einer Komponente:
|
|
const day = useDaySnapshot();
|
|
const streaks = useStreaks();
|
|
const nudges = evaluateRules(day.value, streaks.value, []);
|
|
console.log(nudges);
|
|
```
|
|
|
|
### 8. IndexedDB direkt inspizieren
|
|
|
|
Alle Companion-Daten liegen in IndexedDB (`mana` Database):
|
|
|
|
| Tabelle | Inhalt |
|
|
|---------|--------|
|
|
| `_events` | Domain Event Stream (type, payload, meta) |
|
|
| `companionGoals` | Aktive Ziele mit currentValue |
|
|
| `companionConversations` | Chat-Gespraeche |
|
|
| `companionMessages` | Chat-Nachrichten + Tool-Calls |
|
|
| `rituals` | Erstellte Rituale |
|
|
| `ritualSteps` | Ritual-Steps (pro Ritual) |
|
|
| `ritualLogs` | Completion-Logs |
|
|
| `_memory` | Extrahierte Muster (nach extractAllPatterns) |
|
|
| `_nudgeOutcomes` | Nudge-Reaktionen |
|
|
|
|
Oeffne: DevTools → Application → IndexedDB → mana → [Tabelle]
|