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.
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 alretractMemoryByIdpromite un rând păstrat, marcat (o urmă de audit, nu ștergere), dar sweep-ul șterge orice rând cuimportance < 0.3, ceea ce un rând retractat are mereu, în clipa în care șidecay_score-ul lui scade sub0.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())) — anteriorGETșiDELETE/PATCHerau 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 viagit hash-object/git update-index— confirmat pringit diff --cachedcă 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 —
memoriesdin motor,nature_observationsembedded din web, sau tipulChunkdin 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:ConversationManageractivează calea pe niveluri doar pentru o instanțăMemoryLifecycle, iarsrc/kelvin.tsconstruiește mereu în schimb unMemoryStoreAdapter, 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 independenteMemoryStoresuprascriind tăcut același fișier partajat, memoria pe termen lung permanent neclasabilă din cauza unui scor hardcodat, un skillforgetcare 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 — decisă la revizia 68 (ștergere) și făcută la revizia 69, vezi amendamentul din src/memory/*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.