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.

P4 — Izolarea memoriei proprietarului și corectitudinea retractării

Revizia 27, 2026-09-09. Prima felie reală de P4. Conform liniei de acceptare a planului — „retained claims are traceable and correctable, users remain isolated, and changes can be evaluated against a baseline" — un pas independent de planificare Opus a delimitat ce e chiar construibil acum față de ce e complet nou, și a găsit că două din cele patru sub-cerințe erau reale, dar incomplete: o proprietate de securitate pe care CHECKPOINT o declara deja închisă, și o cale de corectare/ștergere care funcționa pentru dashboard dar se scurgea înapoi prin alte două căi de recuperare.

#Izolarea memoriei proprietarului, închisă la HEAD pentru prima dată

Revizia 17 din CHECKPOINT afirmă: „GET /api/kiwi/knows este engine-backed doar pentru un browser admin fără authorization; celelalte conturi primesc payload-uri embedded per-utilizator." Asta era adevărat doar în copia de lucru. Rutele comise la HEAD web/src/app/api/kiwi/{status,memory,knows}/route.ts blocau toate ramura motorului doar pe isAdminUser() — care rezolvă doar sesiunea NextAuth, niciodată antetul Authorization (isAdminUser() din web/src/lib/access.ts vs. currentUserIdFrom(), care prioritizează corect o cheie API față de sesiune). O cerere care poartă atât un cookie de sesiune admin valid, CÂT ȘI un token de mașină Bearer kiwi_ak_… — realist ori de câte ori browserul admin-ului însuși are și o sesiune autentificată prin agent-key, de exemplu via CLI sau un agent de dispozitiv — ar citi memoria privată a motorului proprietarului printr-o cale menită să fie per-cont, scopată la mașină. Exact bypass-ul împotriva căruia currentUserIdFrom protejează deja peste tot altundeva (/api/devices aplică regula identică).

Reparat prin rezolvarea identității și verificarea antetului ÎNAINTE ca ramura admin să fie măcar încercată — status.ts încerca înainte engineFetch și rezolva identitatea doar în blocul catch, o ordine fail-open, nu doar o verificare lipsă. O a doua instanță, independentă, a aceluiași gol: DELETE/PATCH din knows/route.ts (retractare/editare memorie) verifica doar isAdminUser() chiar și în copia de lucru — o credențială de mașină putea muta memoria proprietarului printr-o cale pe care nici măcar n-o putea citi. Reparat identic.

#Corectitudinea retractării — trei scurgeri reale închise

retractMemoryById (src/mind/database/database.ts, deja comis, fără diff pendinte) pune importance pe zero și marchează metadata.retracted = true — dar trei din cele patru căi surori de citire nu excludeau niciodată rândurile retractate:

  • getMemoriesWithEmbeddings — căutarea semantică punctează similaritate*0.7 + importanță*0.2 + decay*0.1; un rând cu importanța pusă pe zero tot trece pragurile obișnuite de recuperare doar din similaritate. Un fapt „uitat" continua să ajungă la model, deși dispăruse din dashboard.
  • getLinkedMemories — graful de asociații continua să scoată la iveală o memorie retractată ca vecin „îmi amintește de..." al uneia active.
  • Ștergerea forțată din decayMemories — propriul docstring al retractMemoryById promite un rând păstrat, marcat (o urmă de audit, nu ștergere), dar sweep-ul șterge orice rând cu importance < 0.3, ceea ce un rând retractat are mereu, în clipa în care și decay_score-ul lui scade sub 0.01. Lăsată neatinsă, o memorie retractată ar fi fost distrusă permanent de tick-ul zilnic în aproximativ trei săptămâni.

Toate trei reparate cu filtrul minim (NOT_RETRACTED, sau echivalentul verificării de metadata în JS) deja folosit consecvent de celelalte cinci funcții de citire ale fișierului.

#Listă de verificare securitate/corectitudine

  • Simetrie de autorizare. Căile de citire și scriere pe memoria proprietarului aplică acum aceeași regulă (req.headers.has('authorization') || !(await isAdminUser())) — anterior GET și DELETE/PATCH erau inconsecvente.
  • Ordine fail-closed. Identitatea se rezolvă înainte ca ramura admin să fie măcar încercată, deci o credențială lipsă/invalidă primește 401 înainte ca oricare dintre creiere să fie atins.
  • Fără suprafață nouă. Reutilizate engineFetch, currentUserIdFrom, isAdminUser — nicio rută nouă, nicio schimbare de schemă pentru această jumătate a feliei.
  • Retractarea rămâne unidirecțională și auditabilă. Toate trei fix-urile exclud rândurile retractate din RECUPERARE; niciunul nu atinge retractMemoryById însuși, deci rândul păstrat, marcat (urma de audit) rămâne neafectat — s-a schimbat doar ce căi îl mai pot scoate la vedere utilizatorului sau modelului.

#Fișier cu fișier

  • web/src/app/api/kiwi/status/route.ts, memory/route.ts, knows/route.ts — fierbinți (fișiere mari, preexistente, necomise, parte din același bucket din era P0 deliberat amânat ca și munca Telegram din reviziile anterioare). Construite din baza reală comisă la HEAD, într-o copie izolată în scratch, nu din copia de lucru, și stagiate via git hash-object/git update-index — confirmat prin git diff --cached că e exact acest fix, nimic din conținutul pendinte din jur.
  • src/mind/database/database.ts — curat (tracked, fără diff pendinte la momentul acestui fix) — cele trei fix-uri de scurgere la retractare, commit obișnuit.
  • tests/userMemory.test.ts — curat, extins cu 3 cazuri noi.
  • web/tests/kiwi-memory-ownership.test.ts — nou (7 cazuri).

#Non-obiective deliberate pentru această felie

  • Proveniență sursă/pagină per-fapt. Niciun strat de stocare — memories din motor, nature_observations embedded din web, sau tipul Chunk din căutarea de cunoștințe — nu duce o referință document/pagină până la o memorie stocată, azi. Proiectarea acelei scheme peste două stocări nelegate, înainte ca vreo cerință reală să-i restrângă forma, ar fi ghicit, nu construit.
  • Un harnas de evaluare („changes can be evaluated against a baseline"). Nu există nimic de comparat — niciun set de fixture-uri, nicio rulare de bază. Construirea unuia înainte de repararea scurgerilor de retractare de mai sus ar fi făcut baseline pe comportamentul cu bug; asta e felia naturală următoare, nu asta.
  • src/memory/* (nivelurile STM/MTM/LTM, MemCon, DiffCoT, commitment, firewalls). Design real, deliberat — dar de neatins la rulare: ConversationManager activează calea pe niveluri doar pentru o instanță MemoryLifecycle, iar src/kelvin.ts construiește mereu în schimb un MemoryStoreAdapter, deci nimic nu se scrie vreodată acolo. Alte câteva defecte găsite citind codul (un pas de promovare care golește 100% din memoria pe termen scurt în loc de jumătatea intenționată, trei instanțe independente MemoryStore suprascriind tăcut același fișier partajat, memoria pe termen lung permanent neclasabilă din cauza unui scor hardcodat, un skill forget care raportează succes fără să șteargă nimic) — raportate, nereparate, fiindcă soarta subsistemului (reparat-și-conectat, sau șters) e o decizie arhitecturală a operatorului, nu un efect secundar al unui fix de proprietate a memoriei.
  • Tabelele SQLite pe niveluri (stm_entries/mtm_nodes/ltm_entries) din același bucket pendinte — schemă reală, dar nimic nu scrie în ele; comiterea tabelelor fără scriitori ar însemna inventarea unei stări.
  • Unificarea celor trei sisteme de memorie (pe niveluri în motor, SQLite plat în motor, embedded per-cont în web) — o decizie arhitecturală reală, care are nevoie de propriul pas de design.

#Verificări

npm run typecheck și npm run typecheck:web: ambele verzi. Motor: 145 teste (144 trecute + 1 skip semantic opțional; +3). Web/mind: 41 suite / 271 teste (+7). npm run verify:imports: 496 fișiere, 0 probleme. Clone izolat (git clone --local --no-hardlinks + npm ci proaspăt + npx tsc --noEmit): curat, cu excepția golului preexistent, nelegat, de migrare stripe/studio documentat la revizia 26 — neschimbat de acest commit. [REZOLVAT la rev 36] stripe/studio au fost șterse definitiv și fiecare importator real migrat; typecheck:web e curat pe checkout proaspăt de atunci. Fără lansare în producție.

#Ce rămâne din P4

Proveniență per-fapt; harnasul de evaluare față de un baseline; soarta subsistemului mort pe niveluri din src/memory/* — decisă la revizia 68 (ștergere) și făcută la revizia 69, vezi amendamentul din docs/adr/0009-three-tier-memory.md; unificarea celor trei sisteme de memorie; golul preexistent de typecheck stripe/studio (revizia 26, încă deschis) — rezolvat la rev 36.

Surse: web/src/app/api/kiwi/{status,memory,knows}/route.ts, src/mind/database/database.ts, web/src/lib/access.ts, tests/userMemory.test.ts, web/tests/kiwi-memory-ownership.test.ts, checkpoint.

~ End of Document ~