Skip to content
←Return to Projects
Case Study

KIWI

Autonomous local AI agent and community of voices living in The Grove with private memory.

The Problem

Autonomous AI agents often suffer from leaky memory isolation, unpredictable side-effects on host systems, and lack of human-in-the-loop governance for high-stakes file operations and external device actions.

My Role

Architected and implemented the cognitive engine ("The Grove"), fail-closed host action safety policies, immutable exact-action plan ledgers, multi-device agent companion protocol, and unified multi-channel gateways.

Highlights

  • Community of six voices living in The Grove with local memory running on the user's machine
  • Local cognitive core ("The Grove") separating owner memory from credentialed client sessions
  • Fail-closed security policy requiring immutable exact plans with rollback for all destructive actions
  • DPAPI-secured Windows companion agent with local execution receipts and anti-re-execution guarantees
  • Multi-modal interaction surface: Next.js Web UI, Voice HUD (:4321), and Telegram bot gateway with inline approvals
  • Deterministic audit ledger tracking every state mutation, file movement, and memory association

Stack

TypeScriptNext.js 15Node.jsSQLiteDrizzle ORMDPAPITelegram Bot APIDocker

Constraints

  • Host safety is non-negotiable: no autonomous file deletion or batch reorganization without human-confirmed exact plans.
  • Offline-first engine: core cognitive loops and memory databases must run purely locally without cloud dependencies.
  • Multi-tenant memory isolation: administrative sessions and API token holders must never access the owner private mind context.

System Architecture

Interaction Channels
Next.js 15 Web App (:3333)
Voice HUD (:4321)
Telegram Bot
Embed Bridge (:3234)
Gateway & Protocol
Private Grove API (:8431)
Device Control API
DPAPI Token Auth
Cognitive Engine ("The Grove")
Mind Core
Behavior & Persona
Memory Tiering (STM/MTM/LTM)
Skill Registry
Execution & Safety
Host Action Policy
Exact-Action Ledger
Backup & Rollback Ledger
Distributed Node Runtime
Windows Companion Daemon
Local File Executor
SQLite / Drizzle State

Key Trade-offs

The decisions worth defending — what I chose, what I turned down, and why.

File management execution model

Chose

Immutable two-phase exact-action plans with hash verification and dry-run preview

Rejected

Direct agent tool execution with broad shell permissions

Agents making speculative shell or file modifications risk catastrophic data loss; two-phase plans guarantee human oversight and rollback capability.

Engine-to-web communication architecture

Chose

Dedicated loopback HTTP/SSE gateway with bearer token authentication

Rejected

Monolithic single-process Next.js application

Decoupling the AI engine process from the Next.js frontend ensures background monitoring, telegram polling, and long-running skills survive web redeployments.

Device pairing security model

Chose

Windows DPAPI machine-bound tokens with polling architecture

Rejected

Inbound SSH or remote socket servers on client machines

Inbound listening ports expose clients to local network scans; outbound HTTPS polling behind DPAPI keeps remote companion devices secure.

What I'd Do Differently

An honest retrospective — the stuff I'd change with more time, more users, or a second pass.

  1. 1Implement automated regression tests for memory graph decay sweeps earlier in the development lifecycle.
  2. 2Consolidate SQLite schema migrations between better-sqlite3 and pg-schema under a single unified migration runner.
  3. 3Add fine-grained telemetry dashboards to observe cognitive latency across multi-turn reasoning chains.

Interactive Autonomous Agent Shell

Test live KIWI autonomous agent supervisor routines (agent status, grove ping, mcp tools, help) in the emulated shell below.

icelegends-interactive-shell ~ [kiwi]
History: ↑ / ↓
kiwi@the-grove:~/voices$

Technical Deep-Dive

Architecture, specifications, and implementation details.

KIWI — Harta codebase-ului

Revizia 17: P0 cap-coadă. Mutările/ștergerile largi de fișiere rulează doar pe planuri exacte persistente (src/exactions/ledger.ts — imutabil, copii de recuperare, ledger data/exactions/ledger.json, execuție numai cu exactPlan:{id,kind} + aprobare umană): documents_organize, disk_doctor reap (rev. 16) și file_organizer (rev. 17, clasificare partajată în src/skills/fileOrganize.ts; automatizarea mind file-organizer e preview-only). dev_task e fail-closed implicit (security.skillPolicy.dev_task:'deny', configPath() onorează KELVIN_CONFIG_PATH). POST /api/documents/root sincronizează kiwi.mind.config.skills — fără drift de țintă până la restart. GET /api/kiwi/knows e engine-backed doar pentru browser admin fără authorization; restul primesc payload embedded per-user. Rutele de chat documente (web+motor) și upload-urile (/api/chat/files, 413 din Content-Length) sunt limitate; scripts/repair-attachment-links.mjs repară message_files în tranzacție. Audit RO / EN, acțiuni gazdă RO / EN. Verificări: 132 teste motor (131 + 1 skip); 228 mind/web în 34 suite; typecheck-uri trecute; fără lansare în producție.

Revizia 9: chatul browser admin folosește POST /api/chat/scoped al motorului. Serverul web derivă owner-web:v1:<sha256> din JSON [authenticatedUserId, ensureSession(...).id]; nu transmite o identitate de motor primită de la browser. Managerul separă aceste istorice chiar cu conversation.shared activat; aprobările rămân legate de identitatea respectivă. Scopul absent/malformat este refuzat, iar brain-ul indisponibil produce 503 fără mind.hear. Motorul confirmă identitatea în răspuns; web refuză nepotrivirea fără retrimitere. Motorul vechi nu are endpoint-ul și nu poate procesa tacit cererea în istoricul comun. Memoria de lungă durată/personalitatea proprietarului rămân comune; nu este izolare completă per cont a motorului. Verificări: 93 teste motor trecute, unul semantic opțional sărit; 194 teste mind/web în 28 suite și ambele typecheck-uri trecute. Fără lansare în producție.

#Evoluție aprobată, încă în implementare

Plan: RO / EN. Harta de mai jos descrie baza existentă, nu arhitectura viitoare deja livrată. P0 verifică starea admin/per-user; P1 adaugă registru de dispozitive Windows, conexiuni inițiate de agent și aprobări aplicate local. Telegram/TUI/OS vor reutiliza sarcinile și identitatea. Pilotul păstrează găzduirea locală. Kernelul Rust rămâne experimental; produsul OS țintă folosește Linux personalizat. Actualizați această hartă când se schimbă contractele reale.

Scop: ca un model să înțeleagă repo-ul în < 2 minute, fără să-l exploreze singur. Citește asta ÎNAINTE de orice modificare. E scurtă intenționat; detaliile rămân în fișiere.

Revizia 8: continuitatea transportului chat trimite chatul JSON și SSE al sesiunii browser admin către același endpoint al proprietarului. Eroarea motorului este explicită și nu schimbă mintea cu cea embedded; browserul nu repetă automat prin JSON cererea eșuată sau incompletă. SSE refuză Authorization, inclusiv împreună cu un cookie admin. Răspunsul proprietarului este livrat integral, fără streaming incremental al modelului. Verificare curentă: 192 teste mind/web în 28 suite, typecheck web, build de producție izolat și probă chat în Edge trecute. Persistența personală, fallback-ul intern al motorului și delimitarea conversațiilor rămân deschise. Fără deploy de producție.

Revizia 7: autorizarea acțiunilor pe gazdă aplică lista permisă în JSON/stream/offline, compune restricțiile configurate și ale apelantului, păstrează politica inițială a aprobării și împiedică textul rutinelor interne să autorizeze modificări. Mutațiile cunoscute cer confirmare; previzualizările sunt complete sau refuzate. Telegram direct reverifică politica. Pornirea dev autonomă, mutările documentelor în lot și curățarea distructivă a discului sunt oprite până la planuri exacte. Verificare în revizia 7: 90 teste motor trecute, unul semantic opțional sărit; 180 teste mind/web în 27 suite trecute. P0 rămâne deschis pentru continuitatea memoriei, automatizări/pluginuri și migrarea țintelor exacte. Fără lansare în producție.

#Două straturi

Corecție P0 în codul local: /api/chat permite motorul gazdă doar sesiunilor browser admin fără Authorization. Conturile obișnuite și cererile cu credențiale folosesc brain-ul embedded per utilizator. SSE browser admin folosește același endpoint al motorului prin owner-chat.ts, cu răspuns integral; SSE obișnuit rămâne embedded. SSE refuză Authorization. chat-stream-client.ts nu retrimite automat cereri eșuate; erorile motorului nu mută chatul spre embedded. Endpoint-ul nou /api/chat/scoped folosește firul derivat din cont/conversație. Ruta veche /api/chat păstrează firul comun; memoria de lungă durată a motorului nu este multi-tenant. Verificări și limite: audit P0. Deploy-ul nu a fost efectuat.

P0 revizia 6: web/src/lib/chat-attachments.ts validează proprietatea ID-urilor înainte de istoric/cotă în ambele rute chat. SQLite creează message_files; verificarea fără scrieri a data/kelvin.db a găsit tabela absentă, fără migrare live în această etapă. Statusul și memoria web aplică aceeași limită admin/embedded ca chatul. Telegram direct cere ID-uri private explicite; confirmările leagă solicitant/chat/mesaj. Managerul păstrează aprobările după identitatea originală telegram:<solicitant>, cu argumente copiate, expirare în cinci minute și reverificarea politicii; istoricul poate rămâne comun. Telegram web are asociere atomică, expirare și sesiuni per proprietar. Auto-resolve are numai unelte de diagnostic. Gateway-ul documentelor refuză unelte arbitrare/mutări, clonează contextul per cerere și folosește chat documentar fără stare comună. Contractul stării descrie memoria volatilă, conversația comună și fallback-ul intern al motorului încă nerezolvate.

P1 local, oprit implicit: src/devices/ este companion-ul Windows cu DPAPI, verificare locală, backup și dovezi împotriva reexecuției. web/src/lib/device-control.ts și /api/devices/[[...path]] gestionează asociere, dispozitive, sarcini și aprobări exacte în SQLite. /dashboard/devices oferă interfața. Agentul face polling HTTPS către web, fără acces implicit la motor. packages/companion/ conține pachetul privat JavaScript compilat prin tsconfig.companion.json. Numai modificări limitate de fișiere în workspace, cu confirmare web și locală. Nu este TUI complet, acces integral la PC sau lansare acceptată. API, instalare și limite.

StratDirectorRuleazăRol
Motorsrc/acest PC (npm run start)AI-ul propriu-zis: minte (The Grove), memorie, persona, TTS/STT, Telegram, watcheri
Webweb/acest PC (next start -p 3333, în spatele tunelului cloudflared)Landing + auth + dashboard chat. Next.js 15 App Router
Companion pilotsrc/devices/ → packages/companion/dist/PC Windows asociat, pornire explicităExecuție locală aprobată, token individual; funcție web implicit oprită

Motorul NU e în cloud. Web-ul vorbește cu motorul printr-un API privat local.

Autentificare/atașamente revizia 6: currentUserIdFrom și descărcarea ISO refuză rezerva cookie pentru credențiale invalide. Upload/listare cer conversație proprie; cota se rezervă tranzacțional. chat-file-storage.ts administrează rădăcina comună web/Telegram (KIWI_CHAT_UPLOAD_DIR), numele generate și ștergerea limitată. Curățarea elimină numai înregistrări explicit expirate, păstrând fișierele necunoscute și cele vechi fără expirare. Contract complet.

#Porturi & legături (toate în src/serve.ts)

PortNumeRol
:3234embed bridgechat widget embed (proiecte externe)
:4321voice HUDmicrofon + TTS din browser
:8431grove-apiAPI privat motor↔web (token kiwi-local implicit)

Web-ul apelează grove-api prin web/src/lib/engine.ts, cu env KIWI_ENGINE_URL / KIWI_ENGINE_TOKEN.

#Motorul (src/)

  • serve.ts — entry point, leagă tot (Telegram, watcheri, bridge, grove-api, TTS).
  • kiwi.ts + kelvin.ts — corpul agentului (conversație, skills, scheduler, memory).
  • mind/ — The Grove (lumea interioară): mind/kelvin.ts e centrul; mind/memory/, mind/behavior/, mind/relationship/, mind/nature/, mind/llm/ (brain-ul), mind/database/.
  • grove-api.ts — rutele HTTP pe care web-ul le consumă (/api/chat, /api/kiwi/*, /api/grove/*, /api/documents/*).
  • config/config.ts — config motor, citit din kiwi.config.json (gitignored).
  • persona/ — personas (papa-ko, grove-charter).
  • tts/, stt/, llm/ — sintetizator/transcriere/brain. TTS provider curent: edge.
  • skills/ — skills invocabile de brain (builtin + plugins din plugins/). Pluginurile declară capabilities (read/write/exec/network/system/media); fără declarație sunt fail-closed (politica src/security/host-action-policy.ts, backstop în registry.run, expunere prin registry.skillDeclaration).
  • exactions/ — ledger-ul planurilor exacte de acțiune (ledger.ts): plan imutabil (id, kind, operațiuni absolute), pregătire cu copii de recuperare (move) / mutare în backup (delete), validare imuabilitate + stare pe disc, execuție (done/partial) și rollback; persistare în data/exactions/ledger.json (respectă KELVIN_DATA_DIR).
  • documents/, watch/, telegram/, embed/, scheduler/, security/, i18n/.

#Web-ul (web/)

  • Rute pagini (App Router): (marketing)/ (landing), (auth)/ (login/register/verify), (dashboard)/ (chat, grove/mind, settings, admin, documents), os/ (preview iframe).
  • Rute API: api/auth/*, api/chat/*, api/documents/*, api/billing*, api/kiwi/*, api/grove/*, api/admin/overview, api/download/iso, api/os/login, api/webhooks/stripe.
  • lib/ — logică partajată: db/ (drizzle + SQLite), engine.ts (bridge spre motor), auth/, email/, billing/, plan-specs.ts, verify.ts, gdpr/.
  • middleware.ts — guard autentificare pe rutele protejate.
  • api/devices/[[...path]] — numai POST-urile exacte de asociere/agent delegă autentificarea din middleware către rută. Deciziile browser cer sesiune, lipsa Authorization și Origin din AUTH_URL/NEXTAUTH_URL (important prin tunel). Corpul JSON este limitat la 100.000 octeți în timpul citirii.

#Baza de date

  • SQLite local: data/kelvin.db (gitignored), partajată motor↔web prin KIWI_DB_PATH.
  • Schema: web/src/lib/db/sqlite-schema.ts (drizzle). pg-schema.ts + web/drizzle.config.ts = cale viitoare Postgres (Neon) — ține-le, zero schimbare de cod dacă migrezi.
  • Nu există Vercel/Neon live — decizie: local + tunel cloudflared.

#Teste

ComandăCe rulează
npm run testnode --test tests/*.test.ts (motor)
npm run test:mindvitest → tests/mind/** + web/tests/**
npm run typechecktsc --noEmit (motor)
npm run typecheck:webtsc --noEmit în web/

Reper revizia 6 (2026-09-08): 177 teste mind/web în 27 suite, typecheck web și compilare companion trecute. Reper motor anterior: 80 trecute, unul semantic opțional sărit, typecheck trecut. Build Next de producție și probă Edge/HTTP trecute într-o copie izolată cu SQLite gol și conturi sintetice. Proba verifică aprobare web, editare temporară reală, izolare, refuz, revocare și persistență după restartul exclusiv al serverului de probă. APPLY local este simulat; nu este acceptare pe două PC-uri. Avertismente JOSE/Edge și înregistrarea schedulerelor la build rămân de urmărit. Comenzi și dovezi. Fără deploy, restart de producție sau activare pilot.

#CI/CD (GitHub Actions)

  • Workflow: .github/workflows/ci.yml — rulează typecheck + teste pe push/PR către master (badge în README.md).
  • Job security (paralel): gitleaks secret-scan (gate) + npm audit raport (non-blocant până se rezolvă advisory-urile fără fix — sharp, xlsx, vite).
  • Fără next build gate pe CI: page-data collection deschide SQLite-ul local (data/kelvin.db), inexistent pe un runner nou → fail fals (infrastructură, nu cod). Vezi comentariul din ci.yml.
  • Fără deploy din Actions: deploy e local (pm2 + tunel), PC-ul nu e accesibil dintr-un runner. Deploy-ul rămâne manual prin skill-ul kiwi-deploy (vezi ADR-0001).

#Deploy

  • Motor: npx pm2 restart kiwi --update-env
  • Web: build în .next-prod + npx pm2 restart kiwi-web --update-env (vezi skill-ul kiwi-deploy)
  • Tunel: ~/.cloudflared/config.yml (modifică aditiv)

#Convenții

  • Codul & comentariile tehnice în engleză; discuția cu Matei în română.
  • Nu comite niciodată: data/, web/.env, kiwi.config.json, *.log, *.iso, build-urile .next*.
  • Chei LLM se pun cifrate (enc:v1:) în kiwi.config.json, NU în cod.

#Diagrame (Mermaid)

##Runtime: două straturi + tunel

graph TB
    subgraph PC["Acest PC (pm2)"]
        subgraph Motor["Motor AI (src/)"]
            GROVE["grove-api :8431<br/>token kiwi-local"]
            EMBED["embed bridge :3234"]
            VOICE["voice HUD :4321"]
        end
        subgraph Web["Web (Next.js, web/)"]
            WEB["next start -p 3333<br/>App Router"]
            ENGINE_LIB["web/src/lib/engine.ts"]
        end
        DB[("data/kelvin.db<br/>SQLite partajată")]
    end

    Internet["Internet"] --> TUN["cloudflared tunel<br/>config.yml"]
    TUN -->|"https://kiwi.ice4legends.com"| WEB
    TUN -->|"/ice4legends/api/*"| EMBED
    WEB --> ENGINE_LIB
    ENGINE_LIB -->|"KIWI_ENGINE_URL / _TOKEN"| GROVE
    Motor --> DB
    Web --> DB

##Flux motor ↔ web (chat browser admin JSON/SSE; conturile obișnuite folosesc embedded)

sequenceDiagram
    participant U as Browser
    participant W as Web (Next.js)
    participant G as grove-api (:8431)
    participant Ki as Kiwi (mind/)
    participant LLM as LLM provider

    U->>W: message
    W->>G: POST /api/chat/scoped (token + conversationId)
    G->>Ki: route + brain
    Ki->>Ki: detectIntent / attitude / memory
    Ki->>LLM: generate (enc:v1: key decriptată)
    LLM-->>Ki: răspuns
    Ki-->>G: răspuns + persona
    G-->>W: JSON complet
    W-->>U: JSON sau adaptor SSE (meta, token complet, done, saved)

##Layout funcțional al straturilor

flowchart LR
    subgraph SRC["src/ — motor AI"]
        S1["serve.ts (entry)"]
        S2["kelvin.ts + mind/"]
        S3["grove-api.ts"]
        S4["persona/ · skills/ · tts/ · stt/ · llm/"]
        S5["documents/ · watch/ · telegram/ · embed/ · scheduler/ · security/ · i18n/"]
    end
    subgraph WEB["web/ — Next.js"]
        W1["(marketing)/ · (auth)/ · (dashboard)/ · os/"]
        W2["api/ (auth · chat · documents · billing · kiwi · grove)"]
        W3["lib/ (db · engine · auth · email · billing)"]
    end

    SRC --> WEB
    WEB --> SRC

ADR-urile deciziilor arhitecturale: vezi docs/adr/.

~ End of Document ~