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
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
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.
- 1Implement automated regression tests for memory graph decay sweeps earlier in the development lifecycle.
- 2Consolidate SQLite schema migrations between better-sqlite3 and pg-schema under a single unified migration runner.
- 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.
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.
| Strat | Director | Rulează | Rol |
|---|---|---|---|
| Motor | src/ | acest PC (npm run start) | AI-ul propriu-zis: minte (The Grove), memorie, persona, TTS/STT, Telegram, watcheri |
| Web | web/ | acest PC (next start -p 3333, în spatele tunelului cloudflared) | Landing + auth + dashboard chat. Next.js 15 App Router |
| Companion pilot | src/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)
| Port | Nume | Rol |
|---|---|---|
:3234 | embed bridge | chat widget embed (proiecte externe) |
:4321 | voice HUD | microfon + TTS din browser |
:8431 | grove-api | API 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.tse 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 dinkiwi.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 dinplugins/). Pluginurile declarăcapabilities(read/write/exec/network/system/media); fără declarație sunt fail-closed (politicasrc/security/host-action-policy.ts, backstop înregistry.run, expunere prinregistry.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 îndata/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 prinKIWI_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 test | node --test tests/*.test.ts (motor) |
npm run test:mind | vitest → tests/mind/** + web/tests/** |
npm run typecheck | tsc --noEmit (motor) |
npm run typecheck:web | tsc --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ătremaster(badge înREADME.md). - Job
security(paralel):gitleakssecret-scan (gate) +npm auditraport (non-blocant până se rezolvă advisory-urile fără fix —sharp,xlsx,vite). - Fără
next buildgate pe CI: page-data collection deschide SQLite-ul local (data/kelvin.db), inexistent pe un runner nou → fail fals (infrastructură, nu cod). Vezi comentariul dinci.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-ulkiwi-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:) înkiwi.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/.