managarten/docs/plans/workbench-templates.md
Till JS a08e45ca16 feat(templates): generalise to WorkbenchTemplate + ship Calmness pilot (T1)
First pass of the workbench-templates plan (docs/plans/workbench-
templates.md) — templates are no longer agent-centric but a general
"starter kit" bundle: optional agent + optional scene + optional
missions + optional per-module seeds. Pilot non-AI template "Calmness"
ships alongside.

Shape generalisation (packages/shared-ai/src/agents/templates/types.ts):
- AgentTemplate renamed to WorkbenchTemplate; all fields now optional
  (agent, scene, missions, seeds). Back-compat AgentTemplate alias
  kept so research/context/today keep compiling.
- Added `category: 'ai'|'wellness'|'work'|'lifeEvent'|'delight'` +
  `icon` (for non-agent templates that have no avatar) + `version`
  field (for future update-detection).
- New WorkbenchTemplateSeedItem shape: `{stableId?, data: unknown}`.
  Module-specific seed payloads are typed at the handler side.
- Existing three AI templates nachgezogen: category='ai' (or
  'delight' for today-agent), icon, version='1'.

Seed infrastructure:
- apps/mana/apps/web/src/lib/data/ai/agents/seed-registry.ts — in-
  memory handler map keyed by module name; module-local seed.ts files
  register themselves at import time.
- apps/mana/apps/web/src/lib/modules/meditate/seed.ts — first handler:
  createPreset-based, idempotent via stableId embedded as HTML
  comment in the preset description (T1 pragmatism; T2 adds a proper
  column on the preset schema).
- data/ai/missions/setup.ts pulls `import '$lib/modules/meditate/seed'`
  so the handler is registered before any template is applied.

Applicator upgrades (data/ai/agents/apply-template.ts):
- Agent step now optional — skipped cleanly when template has no
  agent part.
- New step 4: seeds. Walks template.seeds, looks up the handler for
  each module, aggregates per-item outcomes (created/skipped-exists/
  failed) into result.seedOutcomes. Missing handler = warning, not
  fatal. Crypto/encryption unchanged — seeds go through the same
  module stores that module code already uses.
- Result shape gains `seedOutcomes: Record<string, SeedOutcome[]>`
  so the gallery can show "3 new, 1 already there".

Calmness pilot (packages/shared-ai/src/agents/templates/calmness.ts):
- category='wellness', NO agent, scene with meditate/mood/journal/
  sleep apps, two meditate preset seeds:
  * 4-7-8 Atmung (breathing preset)
  * Body-Scan 10min (bodyscan preset with 9 scan steps)
- Each seed has a stableId so re-apply is idempotent.

Gallery updates (routes/(app)/agents/templates/+page.svelte):
- Card avatar falls back to t.icon when no agent. "Agent" chip shows
  only for agent-templates; "N Seeds" chip shows for templates with
  seeds.
- Detail header shows "Workbench-Setup ohne AI-Agent" when no agent.
- New "Seeds" preview section: lists per-module counts + item names.
- Options section gains a "Seed-Daten in Module einpflegen" checkbox.
- Success panel shows seed summary: "3 Seeds neu, 1 bereits
  vorhanden".

Tests: shared-ai 26/26, webapp svelte-check 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 01:07:41 +02:00

16 KiB

Plan: Workbench Templates — Starter-Kits über Module hinweg

Status: Draft, 2026-04-16. Phase T1 (Shape-Generalisierung) + Pilot "Calmness" startet jetzt; T2-T5 folgen iterativ. Scope: Erweitere das existierende Agent-Templates-System (multi-agent-workbench.md Phase 5) so dass Templates nicht nur Agents bündeln, sondern komplette Workbench-Starter-Kits: Scene + Agent (optional) + Missionen (optional) + Seed-Daten in beliebigen Modulen (optional). Motivation: Templates wie "Fitness", "Calmness", "Deep Work" haben immensen Onboarding-Wert, brauchen aber keinen AI-Agent — nur Scene + vor-gefüllte Records (Habits, Goals, Meditate-Presets, …). Der heutige AgentTemplate-Shape zwingt einen Agent ins Bundle. Das wollen wir auseinanderziehen. Verwandte Docs: multi-agent-workbench.md, ../architecture/COMPANION_BRAIN_ARCHITECTURE.md, Ideas-Backlog ../future/AI_AGENTS_IDEAS.md.


Entscheidungen (baked in)

Frage Entscheidung Begründung
Template-Typ-Name WorkbenchTemplate (umbenennen von AgentTemplate) mit optionalen Feldern für Agent, Scene, Seeds, Missions Ehrlicher Name. AgentTemplate wäre ein Sub-Use-Case. Migrations-Path via Alias.
Agent ist jetzt optional agent?: AgentConfig statt required Wellness-Templates ohne AI sollen machbar sein
Kategorisierung category: 'ai' | 'wellness' | 'work' | 'lifeEvent' | 'delight' Gallery kann nach Kategorie gruppieren; Discoverability verbessert sich
Seed-Daten pro Modul Template hat seeds: { [moduleName]: SeedItem[] } — pro Modul ein kleines Array pre-gefüllter Records Flexibel, modul-lokal typisiert, keine Cross-Contamination
Seed-Handler-Registry Jedes Modul das seedable sein will exportiert eine seed.ts mit { moduleName, seedFn(items) }. Template-Applicator hat eine Registry SEED_HANDLERS die sie aggregiert. Mirror des input-resolvers-Musters. Module bleiben autonom; Templates sind durable-lokal typed.
Seed-Idempotenz Jedes Seed-Item hat optional stableId. Wenn der Applicator den Seed mit stableId schon findet, skipped er ihn und wirft eine Warning. Seeds ohne stableId werden immer neu erstellt. Beides hat Use-Cases. Stable-IDs für "kanonische" Seeds (z.B. "Fitness 10-min Meditation"), UUIDs für "bei jedem Apply neu".
Partial-Apply Wie Agent-Templates heute — Fehler pro Seed-Slot in result.warnings, aber Gesamt-Apply blockiert nicht Konsistent mit dem existierenden Applicator.
Versionierung Template-Config hat version: '1' Feld. Zukünftiges "Update verfügbar" ist Phase T5; jetzt reicht die Versionierung als Metadaten damit wir später die Logik dranhängen können. Billige Vorkehrung.
Backward-Compat für AgentTemplate Type-Alias export type AgentTemplate = WorkbenchTemplate. Existierende Templates (research/context/today) bleiben unverändert. Null Regression.
Gallery-Kategorien Ab T1 nur flach gerendert. Ab T3 Gruppierung/Filter. Incremental.

Daten-Modell

Neuer Template-Typ

// packages/shared-ai/src/agents/templates/types.ts (erweitert)

export type WorkbenchTemplateCategory = 'ai' | 'wellness' | 'work' | 'lifeEvent' | 'delight';

export interface WorkbenchTemplateSeedItem {
	/** Wenn gesetzt: Applicator sucht einen existierenden Record mit
	 *  demselben `stableId` und überspringt bei Treffer. Wenn unset:
	 *  Applicator erzeugt bei jedem Apply einen neuen Record. */
	readonly stableId?: string;
	/** Modul-spezifische Payload. Der SeedHandler des Moduls kennt die
	 *  Struktur. Type-Sicherheit über Generic-Parameter in den einzelnen
	 *  Template-Konstanten. */
	readonly data: unknown;
}

export interface WorkbenchTemplate {
	readonly id: string;
	readonly version: string;                  // '1' zunächst
	readonly label: string;
	readonly tagline: string;
	readonly description: string;
	readonly category: WorkbenchTemplateCategory;
	readonly color: string;
	/** Icon-Emoji für die Gallery-Karte (wenn kein Agent dabei ist). */
	readonly icon: string;

	// Alle folgenden Felder optional — ein Template kann Agent-only,
	// Scene-only, Seeds-only oder jede Kombination sein.
	readonly agent?: WorkbenchTemplateAgentPart;
	readonly scene?: WorkbenchTemplateScenePart;
	readonly missions?: readonly WorkbenchTemplateMissionPart[];
	/** Modul-Name → Seed-Items für dieses Modul. Template-Applicator
	 *  schaut die Seed-Handler-Registry durch; unbekannte Module
	 *  werden als Warning gemeldet, kein hartes Fail. */
	readonly seeds?: Readonly<Record<string, readonly WorkbenchTemplateSeedItem[]>>;
}

/** Backward compat: die existierenden research/context/today Templates
 *  typieren sich weiterhin als AgentTemplate = WorkbenchTemplate. */
export type AgentTemplate = WorkbenchTemplate;

Seed-Handler-Registry

// apps/mana/apps/web/src/lib/data/ai/agents/seed-registry.ts

export interface SeedHandler {
	/** Modul-Name, korrespondiert mit dem Key in Template.seeds. */
	readonly moduleName: string;
	/** Wird aufgerufen mit allen Items für dieses Modul.
	 *  Soll Idempotenz-Handling (stableId-Check) selbst erledigen.
	 *  Gibt pro Item zurück ob es neu angelegt oder geskipped wurde. */
	readonly apply: (items: readonly WorkbenchTemplateSeedItem[]) => Promise<SeedOutcome[]>;
}

export interface SeedOutcome {
	readonly stableId?: string;
	readonly outcome: 'created' | 'skipped-exists' | 'failed';
	readonly error?: string;
}

export function registerSeedHandler(handler: SeedHandler): void { ... }
export function getSeedHandler(moduleName: string): SeedHandler | undefined { ... }

Jedes seedable Modul exportiert einen Handler, z.B.:

// apps/mana/apps/web/src/lib/modules/meditate/seed.ts
import { registerSeedHandler } from '$lib/data/ai/agents/seed-registry';
import { meditateStore } from './stores/meditate.svelte';

registerSeedHandler({
  moduleName: 'meditate',
  async apply(items) { ... },
});

Phasen

Phase T1 — Shape-Generalisierung + Calmness-Pilot (dieser Durchlauf)

  • WorkbenchTemplate Typ in shared-ai; AgentTemplate als Alias
  • Existierende 3 Templates (research/context/today) bekommen icon + version: '1' + explizite category: 'ai'
  • seeds-Feld im Typ deklariert (unused bei den 3 bestehenden)
  • Webapp: seed-registry.ts mit registerSeedHandler / getSeedHandler
  • apply-template.ts erweitert um Seeds-Schritt (iteriert template.seeds, ruft passenden Handler, aggregiert Warnings)
  • Pilot-Template "Calmness"category: 'wellness', keine Agent, Scene meditate · mood · journal · sleep, 2 Meditate-Preset-Seeds (z.B. "4-7-8 Atmung" + "Body-Scan 10min")
  • Meditate-Modul exportiert seed.ts mit seedHandler der createPreset ruft
  • Gallery zeigt neuen Template ohne Kategorie-Filter (flach); Detail-Panel rendert "Seeds" Sektion wenn template.seeds gesetzt

Ziel: Calmness-Template funktioniert Ende-zu-Ende. User klickt "Calmness" in der Gallery → Scene wird angelegt → Meditate hat 2 neue Presets → kein Agent, keine Mission. Der Weg ist etabliert; weitere Templates sind dann nur Konfiguration.

Phase T2 — Seed-Handler für Kern-Module (~3-4 Tage)

Module die den größten UX-Gewinn aus Seeding haben:

  • habits — Habit-Seeds ("Täglich trainieren", "8h Schlaf")
  • goals — Goal-Seeds (Weekly / Monthly Ziele)
  • todo — Task-Seeds (Einmal-Tasks für Onboarding)
  • food — Nutrition-Target-Seeds (Kalorien, Wasser)
  • meditate — schon in T1
  • drink — Drink-Preset-Seeds
  • places — POI-Seeds (z.B. "Lieblings-Café hinzufügen")

Jedes Seed-Handler-Modul:

  • exportiert seed.ts mit registerSeedHandler
  • ist idempotent über stable-id (wo sinnvoll)
  • loggt die Outcome-Liste für die Template-Applicator-Warnings

Phase T3 — 6 non-AI Templates + Kategorie-Filter (~3-5 Tage)

  • 🏋️ Fitness — Scene + habits + goals + stretch-Routinen
  • 🧘 Calmness — Scene + meditate-Presets (von T1 übernehmen)
  • 💻 Deep Work — Scene + habits + times-Projekte + leere Todo
  • ✈️ Travel — Scene + places-Kategorien + calendar-Block-Template
  • 🎓 Lernen — Scene + skilltree-Presets + cards-Decks
  • 🌙 Schlaf-Routine — Scene + sleep-hygiene-checkliste + meditate-Presets

Gallery-Enhancements:

  • Kategorie-Tabs oben ("Alle · 🤖 AI · 🧘 Wellness · 💼 Arbeit · 🎉 Lebensereignis")
  • Beliebt-Sektion (Usage-Stats später; jetzt manuell kuratiert)
  • Template-Karten zeigen ihre Komponenten als Chips ("🧘 Presets · Scene · 2 Habits")

Phase T4 — Update-Erkennung + bessere Delete-Story (~2 Tage)

  • Template-Version wird am Scene gespeichert ("diese Scene basiert auf Calmness v1")
  • Wenn Template v2 veröffentlicht wird: In-UI Hinweis "Update verfügbar — fügt 1 Seed hinzu" mit Apply-Button
  • Scene-Delete fragt "Auch Seeds + Agent entfernen?" (default: nein — Seeds können anderweitig nützlich sein)

Phase T5 — User-Created Templates (~1 Woche)

  • Export-Scene-als-Template (inkl. User-Daten-Snapshots wenn User opt-in) → JSON
  • Import-Flow in der Gallery ("Template-Datei laden")
  • Community-Templates via "Template Teilen"-Link (kopiert JSON in Clipboard für jetzt; Future: Template-Share-Endpoint)

Design-Details für Phase T1

Wie "Calmness" konkret aussieht

// packages/shared-ai/src/agents/templates/calmness.ts

export const calmnessTemplate: WorkbenchTemplate = {
  id: 'calmness',
  version: '1',
  label: 'Calmness',
  tagline: 'Atem, Stille, ruhige Momente',
  description: `Ein Workbench-Setup für Stille-Momente. Legt dir eine Szene mit
den Modulen Meditate, Mood, Journal und Sleep an und seed-ed zwei Einstiegs-
Meditationen — mehr brauchst du nicht um anzufangen.

Kein AI-Agent. Du meditierst, nicht dein Computer.`,
  category: 'wellness',
  color: '#8B5CF6',
  icon: '🧘',
  scene: {
    name: 'Calmness',
    description: 'Ruhe, Atem, Stille',
    openApps: [
      { appId: 'meditate', widthPx: 540 },
      { appId: 'mood', widthPx: 340 },
      { appId: 'journal', widthPx: 440 },
      { appId: 'sleep', widthPx: 340 },
    ],
  },
  seeds: {
    meditate: [
      {
        stableId: 'template-calmness:preset:4-7-8',
        data: {
          name: '4-7-8 Atmung',
          description: 'Beruhigende Atemtechnik. Einatmen 4s, halten 7s, ausatmen 8s.',
          category: 'breathing',
          breathPattern: { inhale: 4, hold: 7, exhale: 8, rest: 0 },
          defaultDurationSec: 300,
        },
      },
      {
        stableId: 'template-calmness:preset:bodyscan-10',
        data: {
          name: 'Body-Scan 10min',
          description: 'Sanfte Aufmerksamkeits-Wanderung durch den Körper.',
          category: 'bodyscan',
          bodyScanSteps: [
            'Spüre deine Füße', 'Spüre deine Beine', 'Spüre dein Becken',
            'Spüre deinen Bauch', 'Spüre deine Brust', 'Spüre deine Arme',
            'Spüre deinen Nacken', 'Spüre deinen Kopf', 'Spüre deinen ganzen Körper',
          ],
          defaultDurationSec: 600,
        },
      },
    ],
  },
};

Seed-Handler für Meditate (konkret)

// apps/mana/apps/web/src/lib/modules/meditate/seed.ts
import { meditateStore } from './stores/meditate.svelte';
import { db } from '$lib/data/database';
import { registerSeedHandler } from '$lib/data/ai/agents/seed-registry';
import type { LocalMeditatePreset } from './types';

interface MeditatePresetSeed {
  name: string;
  description?: string;
  category: 'silence' | 'breathing' | 'bodyscan';
  breathPattern?: { inhale: number; hold: number; exhale: number; rest: number };
  bodyScanSteps?: string[];
  defaultDurationSec?: number;
}

registerSeedHandler({
  moduleName: 'meditate',
  async apply(items) {
    const outcomes = [];
    for (const item of items) {
      const seed = item.data as MeditatePresetSeed;
      // Stable-id idempotency: search Dexie for an existing preset
      // with `templateStableId = item.stableId`. Preset schema doesn't
      // have that column today; we stash it in `description` for T1.
      // T2 adds a proper column.
      if (item.stableId) {
        const existing = await db
          .table<LocalMeditatePreset>('meditatePresets')
          .filter((p) => !p.deletedAt && p.description?.includes(`\`${item.stableId}\``))
          .first();
        if (existing) {
          outcomes.push({ stableId: item.stableId, outcome: 'skipped-exists' });
          continue;
        }
      }
      try {
        await meditateStore.createPreset({
          ...seed,
          description: seed.description
            ? `${seed.description}\n\n\`${item.stableId ?? ''}\``
            : `\`${item.stableId ?? ''}\``,
        });
        outcomes.push({ stableId: item.stableId, outcome: 'created' });
      } catch (err) {
        outcomes.push({
          stableId: item.stableId,
          outcome: 'failed',
          error: err instanceof Error ? err.message : String(err),
        });
      }
    }
    return outcomes;
  },
});

Note: der "stableId im description einbetten"-Trick ist Pragmatik für T1. T2 führt eine proper templateStableId Spalte ein wenn wir mehr Module seeden.


Dateien (neu / modifiziert in T1)

Neu:

  • packages/shared-ai/src/agents/templates/calmness.ts
  • apps/mana/apps/web/src/lib/data/ai/agents/seed-registry.ts
  • apps/mana/apps/web/src/lib/modules/meditate/seed.ts

Modifiziert:

  • packages/shared-ai/src/agents/templates/types.ts — generalisierter Shape
  • packages/shared-ai/src/agents/templates/index.ts — Calmness exportieren
  • packages/shared-ai/src/agents/templates/research.ts, context.ts, today.tscategory: 'ai', icon, version: '1' nachreichen
  • apps/mana/apps/web/src/lib/data/ai/agents/apply-template.ts — Seeds-Schritt
  • apps/mana/apps/web/src/routes/(app)/agents/templates/+page.svelte — Detail-Panel zeigt "Seeds" Sektion
  • apps/mana/apps/web/src/lib/data/ai/missions/setup.ts — lädt meditate/seed beim startMissionTick damit der Handler registriert ist

Risiken + Mitigation

Risiko Mitigation
Seed-Handler-Registrierung zur Laufzeit nicht garantiert (z.B. Lazy-Loading bricht Registry) T1: Handler werden in startMissionTick explizit eingebunden (import '$lib/modules/meditate/seed'). T2: Module-Registry aufbohren.
Seed schreibt in encrypted-Tabelle ohne User-Master-Key Seed-Handler ruft die Modul-Stores (die encryptRecord schon nutzen) — Crypto-Pfad unverändert. Funktioniert nur bei unlocked vault (wie alle Writes).
Stable-ID-Hack via description ist hässlich T1 akzeptiert das als pragmatisch. T2 führt echte Spalte + Dexie-Migration ein.
Template-Versionierung ohne Update-Logik Version wird gespeichert, aber keine Update-UI in T1. Vorbereitung für T4.

Nicht-Ziele T1

  • Keine Kategorie-UI (T3)
  • Keine Update-Detection (T4)
  • Keine User-Created Templates (T5)
  • Keine echte Dexie-Spalte für stableId (T2) — T1 nutzt description-Trick
  • Kein "Template anwenden v2" Upgrade-Pfad (T4)

Offene Fragen

  1. Seed-Schemas typisieren? Heute sind Seeds data: unknown. Jeder Handler casted intern. Alternative: jedes Modul exportiert seinen Seed-Typ, Template referenziert ihn. Tight-coupling vs. Flexibilität. → T2 Entscheidung.
  2. Scene-Apps validieren? Wenn ein Template eine appId: 'foo' referenziert die es nicht gibt, erscheint ein broken Tab. T2: Warnings auswerfen statt silently leere Tab. → T2.
  3. Delete-Cascade? Wenn User die Calmness-Scene löscht, sollen die 2 Presets weg sein? Meine Tendenz: nein — Presets sind "Vorschläge", User bewertet selbst ob sie die behalten will. Scene ist Layout, Seed ist Inhalt.