From 0f3fd4eebda5821589486c566cc8734cd537f13d Mon Sep 17 00:00:00 2001 From: Till JS Date: Tue, 14 Apr 2026 21:05:30 +0200 Subject: [PATCH] docs(ai): document Actor attribution + AI Workbench pilot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - COMPANION_BRAIN_ARCHITECTURE §20: Actor model, policy layer, pendingProposals lifecycle, ghost-UI pilot, roadmap, open follow-ups, manual test snippet - DATA_LAYER_AUDIT §9: new Actor columns on records (`__lastActor`, `__fieldActors`), `pendingProposals` table, write-path diagrams for user / AI / approval, open mana-sync Go + Postgres work - apps/mana/CLAUDE.md: short AI Workbench section with pointers + Dexie hook now lists actor stamping Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/mana/CLAUDE.md | 16 ++- .../apps/web/src/lib/data/DATA_LAYER_AUDIT.md | 49 ++++++++ .../COMPANION_BRAIN_ARCHITECTURE.md | 111 ++++++++++++++++++ 3 files changed, 174 insertions(+), 2 deletions(-) diff --git a/apps/mana/CLAUDE.md b/apps/mana/CLAUDE.md index bec0616a0..809868ace 100644 --- a/apps/mana/CLAUDE.md +++ b/apps/mana/CLAUDE.md @@ -70,7 +70,8 @@ table.add(encryptedRecord) ← Dexie write Dexie hooks (database.ts): - stamp userId - stamp __fieldTimestamps per field - - record into _pendingChanges (tagged with appId) + - stamp __lastActor + __fieldActors (user / ai / system — see AI Workbench) + - record into _pendingChanges (tagged with appId + actor) - record into _activity │ ▼ @@ -166,11 +167,22 @@ pnpm test:e2e # Playwright Svelte 5 runes are mandatory — no legacy `let count = 0; $: doubled = count * 2`. Always `$state`, `$derived`, `$effect`. See [`.claude/guidelines/sveltekit-web.md`](../../.claude/guidelines/sveltekit-web.md). +## AI Workbench (in progress) + +The companion is being rebuilt into a **second actor** that works alongside the human in every module. Foundation shipped 2026-04-14: + +- **Actor attribution** — every event, record, and sync row carries `{ kind: 'user' | 'ai' | 'system' }`. Code: `src/lib/data/events/actor.ts`. +- **AI policy** — per-tool `auto | propose | deny`. AI-attributed tool calls default to `propose`, which stages an intent in `pendingProposals` instead of writing. Code: `src/lib/data/ai/policy.ts`. +- **Proposal inbox** — drop `` into any module page to render pending proposals inline as ghost cards with approve/reject. Wired in `/todo` as pilot. + +Full architecture + roadmap (Missions, Runner, Workbench lens, server-side runner): [`docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md` §20](../../docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md). + ## Reference Documents | Path | Purpose | |------|---------| -| [`apps/web/src/lib/data/DATA_LAYER_AUDIT.md`](apps/web/src/lib/data/DATA_LAYER_AUDIT.md) | Data-layer + sync deep dive, encryption rollout, threat model, backlog | +| [`apps/web/src/lib/data/DATA_LAYER_AUDIT.md`](apps/web/src/lib/data/DATA_LAYER_AUDIT.md) | Data-layer + sync deep dive, encryption rollout, threat model, Actor attribution, backlog | +| [`docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md`](../../docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md) | Companion brain + AI Workbench (Actor, Policy, Proposals, Missions roadmap) | | `apps/docs/src/content/docs/architecture/security.mdx` | User-facing security walkthrough | | `apps/docs/src/content/docs/architecture/authentication.mdx` | Auth flow + JWT structure | | [Root `CLAUDE.md`](../../CLAUDE.md) | Monorepo overview, services, dev commands, env vars | diff --git a/apps/mana/apps/web/src/lib/data/DATA_LAYER_AUDIT.md b/apps/mana/apps/web/src/lib/data/DATA_LAYER_AUDIT.md index 8c1625508..b041ed263 100644 --- a/apps/mana/apps/web/src/lib/data/DATA_LAYER_AUDIT.md +++ b/apps/mana/apps/web/src/lib/data/DATA_LAYER_AUDIT.md @@ -590,3 +590,52 @@ Zero-Knowledge-User: bis zum MK-Transfer-Pfad (M5) können sie sich selbst resto - **Signatur**: Ed25519 über `manifest.json` gegen Tampering — heute nur sha256 über events.jsonl - **Resumable Download**: Multi-GB-Accounts werden irgendwann fraglich im Browser - **`_appliedEventIds` Dedup-Tabelle**: Performance-Optimierung für Re-Import (heute macht LWW den Dedup, aber wir verarbeiten trotzdem jedes Event) + +## 9. Actor-Attribution & AI-Workbench (ab 2026-04-14) + +Jeder Write — User, AI, Subsystem — trägt ab jetzt einen expliziten `Actor`. Die Data-Schicht ist Source of Truth: ambient Context (`runAs`) ist nur Ergonomie-Schicht an definierten Boundaries, Primitive frieren den Actor synchron ein. + +### Schreib-Pfade + +``` +User-UI → tool.execute() → DB-Write + hook stamps actor from getCurrentActor() → { kind: 'user' } + __lastActor, __fieldActors, Event.meta.actor, _pendingChanges.actor ← alle tragen actor + +AI-Orchestrator → executeTool(name, params, aiActor) + → policy.resolvePolicy() === 'propose' + → createProposal(...) [staged in pendingProposals, NICHT geschrieben] + +User approved Proposal → approveProposal(id) + → runAsAsync(aiActor, () => executeToolRaw(...)) + → wie User-Pfad, aber alle Stempel = { kind: 'ai', missionId, ... } +``` + +### Neu im Record-Schema + +| Feld | Typ | Zweck | +| --------------- | ---------------------- | -------------------------------------------------------------------- | +| `__lastActor` | `Actor` | Wer hat den Record zuletzt als Ganzes geschrieben (Workbench-Badges) | +| `__fieldActors` | `Record` | Parallel zu `__fieldTimestamps`, fuer feldweise Diffs | + +Beide werden vom Dexie-Hook gestempelt. `isInternalKey` strippt sie aus dem Sync-Payload — sie sind rein lokales Bookkeeping. + +### Neue Tabellen + +| Tabelle | Sync? | Inhalt | +| ------------------ | ------------ | ----------------------------------- | +| `pendingProposals` | Nein (lokal) | Staged AI-Intents awaiting approval | + +Proposals syncen **nicht** — der approved Write läuft normal durch den Modulpfad und syncet mit AI-Actor attribuiert. Indexe: `id, status, createdAt, missionId, [status+createdAt]`. + +### Policy + +`apps/mana/apps/web/src/lib/data/ai/policy.ts` — per-Tool `auto | propose | deny`. User/System Actors umgehen die Policy. Default konservativ: alles Mutierende → `propose`. + +### Offene Server-Seite + +`_pendingChanges.actor` landet im mana-sync Payload. Der Go-Server muss das Feld annehmen und in Postgres persistieren — separater Migration-Step. Bis dahin verwirft mana-sync das Feld stillschweigend (unknown fields werden ignoriert), cross-device Attribution fehlt. + +### Deep-Dive + +Architektur-Doku mit Roadmap + manuellen Tests: [`docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md` §20](../../../../../../docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md). diff --git a/docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md b/docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md index 0529d53f8..313877ea8 100644 --- a/docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md +++ b/docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md @@ -1673,5 +1673,116 @@ Alle Companion-Daten liegen in IndexedDB (`mana` Database): | `ritualLogs` | Completion-Logs | | `_memory` | Extrahierte Muster (nach extractAllPatterns) | | `_nudgeOutcomes` | Nudge-Reaktionen | +| `pendingProposals` | Staged AI-Intents (siehe §20) | Oeffne: DevTools → Application → IndexedDB → mana → [Tabelle] + +--- + +## 20. AI Workbench (ab 2026-04-14) + +Der Companion wird schrittweise vom **Chatbot-mit-Tools** zum **zweiten Akteur im System**: +er arbeitet parallel zum Menschen in den bestehenden Modulen, User sieht jede Aenderung +inline und approved / reverted wo noetig. Fundament laeuft; Missions + Runner folgen. + +### 20.1 Actor-Modell + +Jeder Write im System traegt ab jetzt einen expliziten Actor. Source of Truth ist die +Data-Schicht (Events + Records + Sync-Payload), nicht ambient Kontext. + +```ts +type Actor = + | { kind: 'user' } + | { kind: 'ai'; missionId; iterationId; rationale } + | { kind: 'system'; source: 'projection' | 'rule' | 'migration' }; +``` + +- **Events**: `EventMeta.actor: Actor` (required — kein Legacy-Fallback) +- **Records**: Dexie-Hooks stempeln `__lastActor` + feldweise `__fieldActors` + (parallel zu `__fieldTimestamps`) +- **Sync-Payload**: `_pendingChanges.actor` geht an mana-sync (Go/Postgres-Migration offen) +- **Ambient-Hilfe**: `runAs(actor, fn)` an definierten Boundaries — Primitive frieren + den Actor synchron ein, bevor er ueber `setTimeout` / `queueMicrotask` verloren geht + +Code: `apps/mana/apps/web/src/lib/data/events/actor.ts` + +### 20.2 Policy-Layer + +AI-Writes werden nicht automatisch ausgefuehrt. Per-Tool-Policy entscheidet: + +| Decision | Bedeutung | +|----------|-----------| +| `auto` | Direkt ausfuehren, Actor in Events + Records stempeln | +| `propose`| Als Proposal in `pendingProposals` stagen, User approved inline | +| `deny` | Refuse — Tool niemals fuer AI zugaenglich | + +Default (`DEFAULT_AI_POLICY`): lesendes / append-only self-state → `auto`, alles Mutierende +→ `propose`. User / System Actors umgehen die Policy. + +Code: `apps/mana/apps/web/src/lib/data/ai/policy.ts` + +### 20.3 Proposals + +```ts +interface Proposal { + id, createdAt, expiresAt?, status: 'pending' | 'approved' | 'rejected' | 'expired'; + actor: { kind: 'ai', missionId, iterationId, rationale }; + missionId?, iterationId?; // fuer Workbench-Queries indiziert + intent: { kind: 'toolCall', toolName, params }; + decidedAt?, decidedBy?, userFeedback?; +} +``` + +Proposals sind **lokal only** — sie syncen nicht. Der approved Write syncet +normal durch den Modulpfad, mit dem AI-Actor attribuiert. + +Approval-Flow: `approveProposal(id)` laeuft das gespeicherte Intent unter +`runAsAsync(aiActor, () => executeToolRaw(...))`. `executeToolRaw` umgeht +die Policy — sonst wuerde sie das Intent sofort wieder in ein Proposal +zurueckwerfen. + +Code: `apps/mana/apps/web/src/lib/data/ai/proposals/` + +### 20.4 Ghost-UI in Pilot-Modul (todo) + +`` ist die **opt-in Komponente**: pro Modulseite +ein Einzeiler. Rendert pending Proposals als dashed Ghost-Karten ueber dem echten +Content — zero UI wenn keine anstehen. Approve / Reject inline. Filter ueber +Tool-Registry: Proposal fuer `create_task` landet auf `/todo`, `create_event` auf +`/calendar`, etc. + +Code: +- `apps/mana/apps/web/src/lib/components/ai/AiProposalInbox.svelte` +- `apps/mana/apps/web/src/lib/data/ai/proposals/queries.ts` + +### 20.5 Roadmap + +- [x] Schritt 1 — Actor-Attribution (Events + Records + Sync-Payload) +- [x] Schritt 2 — Policy-Config + `pendingProposals` + Propose-Path im Executor +- [x] Schritt 3 — Ghost-UI im Todo-Pilot (``) +- [ ] Schritt 4 — Missions-Datenmodell + Planner-LLM-Task +- [ ] Schritt 5 — In-App MissionRunner (Foreground) +- [ ] Schritt 6 — Workbench-Timeline-Lens (cross-module AI-Aktivitaet) +- [ ] Schritt 7 — Server-side `mana-ai` / `mana-companion` Bun-Service + +### 20.6 Offene Follow-ups + +- **mana-sync (Go) + Postgres-Migration** fuer `actor`-Feld im pendingChange-Payload +- **System-Actor** in Projections + Rule-Engine wrappen (heute im User-Kontext) +- **Inbox-Rollout** auf weitere Module (Kalender, Notes, …) sobald Tools dort + in `DEFAULT_AI_POLICY` eingetragen sind + +### 20.7 Manueller Test + +Browser-Console auf `/todo`: + +```js +const { executeTool } = await import('/src/lib/data/tools/executor'); +await executeTool( + 'create_task', + { title: 'Test von der KI' }, + { kind: 'ai', missionId: 'demo', iterationId: '1', rationale: 'Beispiel-Proposal' } +); +``` + +Ghost-Karte erscheint sofort ueber der Task-Liste.