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.
P0 — Continuitatea transportului chat
Revizia 13, 2026-09-09: recuperarea după cădere pentru trimiterile web de chat ale proprietarului (închide reconcilierea rămasă deschisă din revizia 12). Când browserul admin se întrerupe mid-turn, motorul finalizează și persistează runda în firul scoped, dar ruta SSE se oprește la stopped() și nu salvează răspunsul assistant. Recuperare: motorul expune GET /api/chat/scoped/thread (src/grove-api.ts, read-only, validează identitatea owner-web:v1:<sha256>) prin ConversationManager.getTurns(chatId) (src/conversation/manager.ts, copie a firului, fără mutare a store-ului), iar web-ul o consumă la GET /api/chat/history?channel=web&sessionId=... pentru conturi admin prin reconcileOwnerChat (web/src/lib/chat-reconcile.ts; bridge ownerChatTurns în web/src/lib/owner-chat.ts). Regula: se repară DOAR ultimul mesaj — dacă ultimul mesaj web este user fără assistant după el și ultima rundă engine completă are același conținut user, se adaugă răspunsul lipsă (max 1 append, idempotent). Firul engine cu user solitar la final = încă generează → nu se reconciliază. Best-effort: eroarea motorului e ignorată/logată, istoricul se servește oricum; multi-gap respins deliberat (append la final ar amesteca ordinea). Verificări: npm run test — 101 trecute (inclusiv 3 noi: getTurns copie fără mutare, endpoint thread validare identitate + răspuns corect, fir gol/inexistent), unul semantic opțional sărit; npm run test:mind — 222 în 33 suite (2 suite noi: chat-reconcile.test.ts + chat-history-reconcile.test.ts); ambele typecheck-uri trecute. Modificări locale; fără restart/deploy de producție.
Revizia 12, 2026-09-09: Telegram nu mai poate livra de două ori același update și nici nu mai poate depăși cota prin concurență. Webhook-ul (web/src/app/api/telegram/webhook/route.ts) captează update_id și îl înregistrează o singură dată în telegram_webhook_updates — cheie (chat_id, update_id) (sigură: getLinkByChatId impune cel mult o legătură activă per chat, iar update_id crește strict per bot), 200 fără procesare la duplicat, curățare a rândurilor mai vechi de 48h. Cota se consumă atomic prin consumeMessageIfUnder (web/src/lib/billing.ts, un singur UPDATE condiționat cu changes === 1), plasat după filtrarea mesajelor numai-fișier și înainte de appendMessage, înlocuind checkUsage+consumeMessage: cereri concurente nu dublează consumul și nu depășesc plafonul. Motorul (src/telegram/run.ts) persistează offset-ul update_id în data/telegram-owner-offset.json (via dataPath, respectă KELVIN_DATA_DIR) și reia de acolo la restart, evitând reluarea ferestrei de ~24h. Reconcilierea după cădere pentru trimiterile web de chat a fost închisă în revizia 13. Verificări: npm run test — 97 trecute, unul semantic opțional sărit; npm run test:mind — 210 în 31 suite (5 teste noi); ambele typecheck-uri trecute. Modificări locale; fără restart/deploy de producție.
Revizia 11, 2026-09-09: ambele rute de chat (JSON și SSE) acceptă acum corpuri de maximum 500.000 de octeți și mesaje de maximum 100.000 de caractere, prin web/src/lib/chat-body.ts (citire în flux, 400 pentru corp lipsă/invalid/mesaj gol, 413 peste limite), iar motorul respinge în chatHandler mesajele non-string sau peste limită cu 413, fără a contacta managerul. Verificări: npm run test — 97 trecute, unul semantic opțional sărit; npm run test:mind — 205 în 30 suite; ambele typecheck-uri trecute. Modificări locale; fără restart/deploy de producție.
Revizia 10, 2026-09-08: declarațiile tehnice de mai jos (concurența pe fir și ștergerea coordonată) sunt verificate; cele care nu pun încă poziția acceptării rămân marcate. Continuă limitarea transportului scoped al proprietarului; nu este acceptare pe două sisteme Windows sau cu furnizori live.
Dovezi revizia 10: npm run test — 96 trecute, unul semantic opțional sărit; npm run test:mind — 200 în 29 suite; ambele typecheck-uri trecute. Sunt nouă teste noi: trei de serializare/ștergere coordonată în manager și pe ruta Grove, șase pe ruta web de ștergere a sesiunii. Modificările de cod rămân locale; fără restart/deploy de producție.
#Revizia 10 — concurența pe fir și ștergerea coordonată
ConversationManager serializează livrările pe fir: reply/replyStream așteaptă orice rundă anterioară prin runSerialized, iar istoricul firului nu mai poate fi intercalat de două cereri concurente în aceeași conversație. Orice fir poate avea o singură rundă în execuție la un moment dat; firele diferite rămân independente.
Ștergerea coordonată unește acum web-ul și motorul la ștergerea unei sesiuni. Motorul expune DELETE /api/chat/scoped, care validează identitatea owner-web:v1:<sha256> așa cum o face POST-ul corespunzător, iar apoi cureață firul prin clearWhenIdle — așteaptă ca orice rundă în curs să se termine înainte de a uita istoricul, astfel încât o ștergere nu poate reînvia un turn aflat în coadă peste un fir golit. Web-ul, în DELETE /api/chat/session, șterge întâi sesiunea SQLite și apoi — numai pentru sesiunile browser ale proprietarului — cere motorului să uite firul scoped cu același ID de conversație. Eroarea motorului nu blochează ștergerea web: e înregistrată și ignorată (store-ul web rămâne autoritar).
Contract: ownerConversationId(userId, sessionId) este singura sursă de adevăr pentru identitatea firului atât la scriere, cât și la ștergere (același sha256 al perechii cont/sesiune). Firele owner-web: rămân separate de canalele partajate și de alte conturi; aprobările continuă să fie legate de identitatea respectivă. Noul DELETE nu acceptă alte identități și nu afectează alte fire ale motorului.
Rămân în P0, neînchise aici: auditul complet al automatizărilor/pluginurilor din afara căii conversație/registru, capabilitățile declarate ale pluginurilor (închise în revizia 15 — vezi acțiuni gazdă) și persistența personală per cont. Reconcilierea după cădere pentru trimiterile web de chat a fost închisă în revizia 13 (vezi mai sus), distinctă de dedup-ul Telegram al reviziei 12. Istoricele vechi nu se importă automat; persistența firelor urmează opțiunea persistConversations existentă; aprobările nu supraviețuiesc restartului.
Dovezi build/browser revizia 9: npm run verify:web trecut, 478 surse în .verify-work/web-NwBtZf/project; verification.json confirmă ieșire 0 și lipsa DB/restartului de producție. node scripts/verify-chat-http.mjs .verify-work/web-NwBtZf/project trecut pe endpoint-ul scoped, cu motor simulat și Edge/HTTP web/SQLite temporar reale. Artefacte: project/chat-probe/743dc227-e3ad-49d8-8ba3-ee9b914156a3/ în copia respectivă. Două POST-uri stream, zero repetare JSON, două apeluri motor, două rânduri user și un assistant. Nu testează furnizori live sau două PC-uri. Erorile tranzitorii de rețea/TLS ale build-ului s-au rezolvat prin reîncercare; avertismentele JOSE/Edge și importul schedulerelor rămân.
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.
Istoricele vechi nu sunt importate automat în noile fire. Reluarea unui chat vechi în UI păstrează afișarea SQLite, dar noul fir al motorului pornește fără transcrierea comună veche. Persistarea firelor folosește opțiunea existentă persistConversations; aprobările nu supraviețuiesc restartului. Ștergerea istoricului web curăță acum și firul scoped al motorului pentru sesiunile browser ale proprietarului (revizia 10). Concurența în același fir este serializată; ștergerea/exportul coordonate pentru toți utilizatorii și memoria persistentă personală rămân deschise. Pentru rollout se actualizează motorul înaintea web-ului; nu există fallback la endpoint-ul vechi. Regresiunile motor folosesc managerul real și ruta Grove reală (inject HTTP, minte simulată), fără acțiuni reale. Testele web verifică hash-ul cont/sesiune, nepotrivirea răspunsului și ștergerea coordonată.
Revizia 9, 2026-09-08. Implementat local; P0 rămâne deschis. Respectă limita cont/operator din ADR-0006 și cerința aprobată de a evita memorii concurente prin fallback.
#Contractul rutării
| Apelant | POST /api/chat | POST /api/chat/stream |
|---|---|---|
| Browser admin fără Authorization | Motorul proprietarului prin ownerChat | Același endpoint prin ownerChatStream |
| Browser obișnuit | Mintea embedded a contului | Mintea embedded a contului |
| Credenciale machine | Identitate validată, verificările existente de abonament, mintea embedded proprie | HTTP 401, inclusiv împreună cu un cookie admin |
| Identitate absentă/invalidă | Refuz | Refuz |
Ambele căi ale proprietarului trimit același mesaj, limba efectivă și notele personalităților către POST /api/chat/scoped al motorului. Adaptorul SSE așteaptă răspunsul JSON complet, apoi emite meta, un singur token cu tot textul și done; ruta persistă răspunsul și emite saved cu ID-urile conversației/mesajelor. Nu simulează scrierea incrementală și nu consultă mintea embedded pentru metadate suplimentare.
Eroarea motorului sau răspunsul invalid/gol nu schimbă mintea. Ruta JSON întoarce HTTP 503 cu ok:false, code:OWNER_CHAT_UNAVAILABLE, source:engine, outcome:unknown, sessionId și userMessageId. După pornirea SSE, eroarea produce un cadru error, fără secvența de succes done/saved. Celelalte erori prinse în blocul de generare JSON întorc și ele HTTP 503, în locul unui răspuns inventat declarat reușit.
#Livrarea în browser și întreruperile
sendChatStream trimite exact un POST. Eroarea de rețea/HTTP/server, tipul neașteptat de răspuns sau fluxul incomplet nu provoacă o cerere JSON de rezervă. Parserul acceptă UTF-8 împărțit între fragmente și cadre CRLF; succesul cere atât done, cât și saved.assistantMessageId.
Dashboard-ul oprește sincron trimiterile suprapuse. ID-ul local al cererii asociază ID-urile DB numai rândurilor acelei cereri; erorile vechi nesalvate nu primesc identitatea unui răspuns ulterior. Atașamentele se golesc numai după finalizare confirmată. Textul parțial este păstrat cu un mesaj vizibil despre întrerupere, iar lista istoricului se reîncarcă.
Anularea oprește livrarea/persistarea ulterioară când este observată înaintea răspunsului. O cerere deja trimisă motorului se poate încheia; revizia 13 recuperează răspunsul rundei în curs la reîncărcarea istoricului sesiunii admin (reconciliere tail-only la GET /api/chat/history). Închiderea browserului nu anulează efectele și nu confirmă oprirea unei acțiuni pe gazdă.
#Consum și limite rămase
Ruta salvează mesajul utilizatorului, asociază atașamentele și consumă cota înainte de apelarea minții. Eroarea nu restituie cota și nu șterge mesajul. Rezultatul necunoscut poate însemna că motorul a lucrat deja. Nu există tranzacție atomică între execuția motorului, istoricul web și consum, deduplicare durabilă a cererilor sau garanția unei singure execuții după cădere/retrimitere manuală; reconcilierea recuperează doar răspunsul pierdut, nu anulează efectele unei runde deja livrate. Înaintea retrimiterii trebuie verificat istoricul/rezultatul acțiunii.
Corecția privește numai transportul browser. Ruta veche fără scope păstrează conversația web și fallback-ul mind.hear; chatul web nou folosește exclusiv managerul și istoric delimitat. Memoria semantică/persona ale motorului rămân comune proprietarului. Memoria embedded rămâne volatilă; celelalte suprafețe Grove și continuitatea Telegram/CLI necesită review. Nu s-au unit, copiat sau șters istorice existente. Bibliotecile PDF sursă sunt păstrate.
#Dovezi anterioare — revizia 8
npm run typecheck:webtrecut;npm run test:mind: 192 teste în 28 suite trecute.- Zece teste de rută verifică echivalența admin JSON/SSE, eroarea explicită, anularea, separarea machine/browser și rutarea contului obișnuit. Șapte teste client verifică lipsa retrimiterii și UTF-8 fragmentat. Testele existente ale atașamentelor continuă să treacă. Autentificarea/mințile sunt simulate în aceste teste unitare.
npm run verify:webtrecut:.verify-work/web-FI79by/project, 477 surse de build; raport.verify-work/web-FI79by/verification.json.node scripts/verify-chat-http.mjs .verify-work/web-FI79by/projecttrecut cu Edge real, HTTP web și SQLite temporar, admin sintetic și motor simulat pe loopback. Două trimiteri au produs două POST-uri stream, zero POST-uri JSON de rezervă și două apeluri motor. DB a păstrat două mesaje utilizator și un răspuns sintetic; apelul eșuat nu a creat rând assistant.- Artefacte în copie:
chat-probe/a930c7aa-ed9a-418b-aa1c-f59f62a0aaf8/;result.jsondeclarăpassed:true, iar captura erorii a fost inspectată. Nu este acceptare cu furnizor/motor propriu live sau două PC-uri. - Reperul motor anterior este revizia 7: 90 trecute, unul semantic opțional sărit. Sursele motorului nu au fost modificate sau retestate în acest pas. Persistă avertismentele JOSE/Edge despre compresie și înregistrarea schedulerelor la build.
Surse: web/src/lib/owner-chat.ts, chat-stream-client.ts, cele două rute chat, grove-main.tsx din dashboard, web/tests/chat-routing-isolation.test.ts, chat-stream-client.test.ts. Urmează proprietatea explicită a conversațiilor și auditul execuțiilor interne/de fundal, apoi persistența și verificările controlate de migrare. Vezi contractul stării, verificarea și checkpoint.