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.

P0 — Ciclul atașamentelor și limitele credențialelor

Revizia 17, 2026-09-09. Corecții locale; fără migrare sau curățare în producție.

#Adăugiri din revizia 17

POST /api/chat/files respinge acum cu 413 din antetul Content-Length când upload-ul depășește plafonul planului contului plus 1 MB de antet multipart, înainte ca formData() să bufferizeze ceva. scripts/audit-attachment-links.mjs e un preflight read-only peste message_files; scripts/repair-attachment-links.mjs (nou) auditează și, la --fix, repară într-o singură tranzacție — deduplică perechile (message_id,file_id) păstrând MIN(rowid), șterge asocierile orfane (mesaj/fișier lipsă) și pe cele cu proprietar greșit; stocarea fișierelor nu e atinsă niciodată. data/kelvin.db de producție raportează încă tableExists:false, migrationRequired:true (tabelul se creează la pornirea web-ului; nicio dată de producție schimbată).

#Autentificare

currentUserIdFrom(req) tratează orice antet Authorization ca alegere explicită a autentificării machine. Credencialele invalide, goale sau revocate nu produc identitate și nu folosesc cookie-ul browserului ca rezervă. Cheile valide selectează în continuare propriul cont. Descărcarea ISO aplică aceeași regulă. API-ul atașamentelor, exclusiv browser, refuză antetele Authorization. Nu declarăm astfel toate API-urile bazate pe sesiune compatibile cu autentificarea machine.

#Încărcare, listare și eliminare

POST /api/chat/files cere un fișier multipart real și o conversație existentă a contului din browser. O conversație străină sau inexistentă întoarce 404 înainte de stocare/extragere. Formularele/fișierele malformate întorc 400. Limitele existente de dimensiune și număr pentru free/pro/enterprise sunt păstrate; nivelurile necunoscute folosesc limitele free.

SQLite rezervă tranzacțional locul atașamentului înaintea scrierii, astfel încât cererile paralele să nu depășească limita conversației. Scrierea eșuată elimină rezervarea și întoarce 503. După scriere se verifică ștergerea concurentă, pentru a nu recrea silențios un atașament eliminat. Nu este tranzacție distribuită; o cădere a procesului poate lăsa un fișier orfan care necesită reconciliere.

Numele pe disc folosesc ID generat și extensie aleasă după MIME pentru web. Documentele/fotografiile Telegram folosesc și ele nume generate; numele primit este numai metadată de afișare. Ambele folosesc implicit uploads/chat, configurabil prin KIWI_CHAT_UPLOAD_DIR. Schimbarea directorului nu migrează căile absolute istorice; acestea se verifică și migrează explicit.

GET cere conversationId, verifică proprietarul și întoarce {files,plan}, fără căile pe disc. POST păstrează câmpurile existente la nivelul răspunsului și adaugă același obiect în file, conform componentei de upload. Componenta trimite acum conversația selectată, cere confirmare înaintea ștergerii și arată succes numai după un DELETE reușit.

DELETE selectează înregistrarea după cont și ID. Poate elimina numai un fișier obișnuit aflat direct în directorul configurat; refuză directoare redirecționate, symlink și hard link. Lipsa fișierului fizic permite eliminarea metadatelor; erorile de cale/filesystem întorc 409 și păstrează metadatele. Ștergerea reușită din DB elimină în cascadă asocierile message_files. Verificările nu sunt certificate împotriva înlocuirii concurente ostile a căilor locale.

#Expirare și curățare programată

Defect confirmat: curățarea Telegram scana directorul comun și elimina toate fișierele mai vechi de 24 de ore după timpul filesystem-ului, inclusiv uploadurile web cu expirare la 30 de zile în DB.

Curățarea selectează acum maximum 200 de înregistrări chat_files explicit expirate per rulare, cu vârstă minimă a înregistrării de 24 de ore implicit. Elimină fișierele administrate și metadatele; erorile păstrează metadatele pentru reîncercare. Nu scanează și nu șterge fișiere necunoscute. Înregistrările vechi fără expires_at și PDF-urile fără metadate rămân intacte. Atașamentele Telegram noi primesc expirare la 30 de zile, asemenea celor web. Programarea existentă la pornire/la șase ore rămâne.

Atașamentele expirate nu apar în GET și nu intră în prompturi noi prin rezolvarea atașamentelor chat, chiar înaintea curățării; nu mai consumă limita activă de upload. Expirarea nu elimină retroactiv textul deja copiat în conversații sau memorie. Acest lucru necesită proveniența/uitarea din P4.

#Dovezi și lucru rămas

web/tests/chat-file-lifecycle.test.ts folosește SQLite și fișiere temporare reale: proprietar, respingere multipart/auth, compatibilitate răspuns, cotă concurentă, ștergere fizică/cascade, căi corupte, fișiere lipsă, junction, retenție și nume Telegram. Extragerea, autentificarea și transportul Telegram sunt simulate. access-identity.test.ts, iso-credential-boundary.test.ts și ambele suite de atașamente chat verifică fallback-ul credențialelor și fișierele expirate.

Rămân: auditul căilor/asocierilor de producție și backup înainte de lansare; limitarea multipart în flux înainte de parsare; reconciliere după căderea procesului; protecție nativă mai puternică împotriva schimbării concurente a căilor; cotă concurentă echivalentă pentru Telegram; uitare/export legate de surse. Nicio operație din această etapă nu modifică Mind memory fundamental/ sau biblioteca PDF sursă.

~ End of Document ~