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 — Autorizarea acțiunilor pe gazdă

Revizia 17 (2026-09-09): file_organizer își închide fluxul exact — automatizarea mind devine preview-only (nu mai mută fișiere), iar skill-ul builtin file_organizer devine singurul executant, sub plan exact validat + aprobare umană; dev_task devine fail-closed implicit (deny în config). Cod local și verificare automată; fără restart sau deploy de producție.

#Fix revizia 17 — plan exact pentru file_organizer și valoarea implicită fail-closed a dev_task

file-organizer (automatizarea mind) nu a putut niciodată muta fișiere (reviziile 7/14/16: non-dryRun refuzat cu motiv exact-plan). Revizia 17 aduce executantul sigur pe care îl aștepta:

  • Modul partajat src/skills/fileOrganize.ts: categorii din EXTENSION_MAP, arbore de destinație coerent, archiveAfterDays → _archive; computeOrganizeMoves(dir, archiveAfterDays) întoarce { moves:[{from,to,size,archived}], skipped } cu ținte anti-coliziune (2) verificate și pe disc; fișierele neclasificate/directoare numărate și lăsate în loc.
  • Skill-ul builtin file_organizer (kind file_organizer, înregistrat în src/skills/builtin/index.ts): la dryRun:true pregătește un plan exact (inclusiv copii de recuperare) prin prepareExactPlan și întoarce același shape data:{ plan, exactPlan:{id,kind}, exactPlanId, backupDir } ca revizia 16; execuția cere exactPlan:{id,kind:'file_organizer'} validat prin validateExactPlan și rulează executeExactPlan; fără referință → motivul de blocare exact-plan.
  • Automatizarea mind file-organizer v1.1.0 e preview-only: afișează output-ul computeOrganizeMoves pentru operator și trimite întotdeauna la planul exact aprobat în chat; nu atinge discul. Politica: builtin file_organizer → allow la dryRun:true, altfel confirm (cu ref valid) — aceeași formă deny-fără-ref ca restul planurilor exacte; automatizarea își păstrează deny pe non-dryRun.
  • dev_task fail-closed implicit: src/config/config.ts livrează acum security.skillPolicy.dev_task:'deny' (era confirm); dev_status/dev_cancel refuză ori de câte ori modul dev e oprit; DevManager.start n-are apelant activ în producție, deci niciun job de codare de fundal nu poate porni. Înlocuitorul sigur rămâne fluxul de sarcini-dispozitiv P1 / codare interactivă P2 — un ledger de mutări/ștergeri nu se potrivește editărilor interactive de cod.
  • Block la nivel de registru extins: pregătirea refuză un kind file_organizer necunoscut, ca la celelalte kinduri.

Dovezi: 4 teste motor noi în tests/exact-plan.test.ts (file_organizer dryRun pregătește + arată țintele; non-dryRun fără plan refuzat; execuție cu plan real validat + aprobare rulează mutările; reverificarea pe disc rămâne) și 2 noi în tests/dev.test.ts (config implicit neagă dev_task; dev_status/dev_cancel fail-closed fără mod dev). Suita motor: 132 teste (131 trecute + 1 skip semantic opțional); mind/web 228 în 34 suite (vezi auditul); ambele typecheck-uri trecute.

#Fix revizia 16 — planuri exacte de acțiune pentru documents_organize și disk_doctor reap

documents_organize (non-dryRun) și disk_doctor reap au fost blocate (reviziile 7/10) cu motivul „plan exact aprobat". Revizia 16 înlocuiește blocul cu un mecanism persistent de planuri exacte, păstrând aprobarea umană la execuție.

Mecanismul (src/exactions/ledger.ts, nou):

  • Plan imutabil: { id: 'ep-<timestamp36>-<hex>', kind: 'documents_organize' | 'disk_doctor_reap', ops }, operațiunile cu căi absolute + size și sha256 (doar pentru move, fișiere ≤16 MB) — imuabilitate verificabilă.
  • prepareExactPlan: validează fiecare operațiune (sursă există, ținta liberă, size>0), face copiile de recuperare pentru move (data/exactions/<id>/moves/N.bin, sub KELVIN_DATA_DIR) și scrie ledger-ul data/exactions/ledger.json cu status:'prepared'.
  • validateExactPlan: înainte de orice execuție reverifică imuabilitatea (sha256 canonizat la {kind, ops}), prezența backup-urilor și starea de pe disc — sursa să existe în continuare, destinația să fie încă liberă.
  • executeExactPlan: move = rename; delete = rename în backupDir/deletes/N (fără rm, recuperabil instant). La prima eroare → status:'partial' + raport {ok:false, done, error}; succes → status:'done' cu executedAt.
  • rollbackExactPlan: restaurează din backup (idempotent); planurile rolledback nu se pot re-încărca. describeExactPlan produce previzualizarea pentru operator.

Politica (src/security/host-action-policy.ts):

  • exactPlanRefOf(args) acceptă doar exactPlan:{id, kind}.
  • legacyActionBlockReason: documents_organize non-dryRun și disk_doctor reap refuză DOAR fără referință validă; builtin file_organizer non-dryRun fără exactPlan și automatizarea file-organizer non-dryRun refuză cu același motiv; dev_task rămâne refuzat necondiționat (config deny).
  • minimumPolicy: pregătirea rămâne allow — documents_organize cu dryRun:true și disk_doctor cu action:plan_reap/report; execuția cu exactPlan valid → confirm (aprobare umană obligatorie prin ConversationManager, nu auto-executare). Fără ref → deny.

Skill-urile:

  • documents.ts: documents_organize cu dryRun:true pregătește planul (clasificare LLM, ținte cu nume anti-coliziune „ (2)"/„ (3)", copii de recuperare) și returnează data:{ plan, exactPlan:{id,kind}, exactPlanId, backupDir }; non-dryRun cere exactPlan, validează contra ledger-ului și execută. Check-ul brain făcut doar în calea de pregătire (execuția nu are nevoie de LLM).
  • diskDoctor.ts: action:'plan_reap' pregătește planul delete (fără ștergere); action:'reap' cere exactPlan, validează și mută folderele în backup. Ștergerea destructivă veche (rm) a fost eliminată.

Dovezi: 11 teste noi în tests/exact-plan.test.ts — plan gol/sursă lipsă refuzate; move prepare→validate→execute→rollback; invalidare la coliziune de destinație; delete reap-style cu backup+rollback; eșec parțial marchează partial; flux complet documents_organize (dryRun pregătește, execuția doar cu ref); flux disk_doctor plan_reap/reap; politică deny→confirm cu/fără ref; aprobare reală prin ConversationManager; shape validation exactPlanRefOf; block la nivel de registru fără plan. Suita motor 124 teste (123 trecute + 1 skip semantic opțional; +11); mind/web 222 în 33 suite (neschimbată); ambele typecheck-uri trecute.

Revizia 15 (2026-09-09): pluginurile de skills declară capabilitățile; fără declarație sunt oprite. Cod local și verificare automată; fără restart sau deploy de producție.

#Fix revizia 15 — capabilități declarate pentru pluginuri (fail-closed)

Revizia 14 a lăsat pluginurile fără metadata de efecte: hostSkillPolicy nu le cunoștea și cădea pe allow implicit (pluginurile nu erau în minimumPolicy), iar apelurile directe la registry.run (document skills în src/grove-api.ts:941, diagnostic CLI în src/cli/kiwi.ts:302) nu treceau prin politica completă — orice plugin arbitrar din pages/plugins putea rula fără niciun check de efecte asupra gazdei.

Correcții:

  • Skill.capabilities declarative (read/write/exec/network/system/media, src/skills/types.ts); plugins/example-dice.mjs declară ['read'].
  • hostSkillPolicy primește un al șaselea parametru declared?: DeclaredSkillInfo (src/security/host-action-policy.ts); declaredSkillPolicy: niciun/fără capabilities → confirm, numai read → allow, orice efect mutațional → confirm. Un allow configurat vechi nu slăbește fail-closed-ul (compunerea reviziei 7: deny > confirm > allow rămâne, pluginul se decide după).
  • SkillRegistry.skillDeclaration (src/skills/registry.ts) expune metadata și alimentează toate căile de politică: Agent.declaredSkill (JSON/stream/offline, src/agent/agent.ts), ConversationManager (src/conversation/manager.ts), Telegram direct (src/telegram/run.ts).
  • Backstop în registry.run: pluginul înregistrat cu sourceFile dar fără capabilities este refuzat înainte de run() (ok:false, motivul capabilităților), deci apelurile directe nu pot ocoli politica. Pluginurile cu declarație rămân rulabile prin agent.runSkill/aprobare (exact ca la executarea confirmată); builtin-ii și skill-urile fără sourceFile nu sunt pluginuri și nu se schimbă.
  • src/skills/plugins.ts loghează un avertisment la încărcarea unui plugin fără capabilities.

Dovezi: 8 teste noi în tests/plugin-capabilities.test.ts (fail-closed cu allow vechi; deny configurat; tool scope; skillDeclaration builtin vs plugin; backstop care nu atinge run(); registry.run direct pe custom builtin rămâne permis). Suita motor 113 teste (112 trecute + 1 skip semantic opțional; +8); mind/web 222 în 33 suite (neschimbată); ambele typecheck-uri trecute.

Revizia 14 (2026-09-09): închide automatizarea mind (mind.hear) și rădăcina documentelor. Cod local și verificare automată; fără restart sau deploy de producție. Reviziile 7 și 10 au rămas în istoricul de mai jos.

#Fix revizia 14 — politica pe automatizarea mind și rădăcina documentelor

Unic apelant de producție al mind.hear este fallback-ul brain-down din src/grove-api.ts:122 (ruta scoped dă 503 la brain indisponibil). Branch-ul de automatizare din hear() executa SkillRunner.execute cu TOATE permisiunile (FileRead/FileWrite/Network/System/Database/Notification) și dryRun:false, fără nicio politică host-action — orice apelant viitor (fereastra de racord ≤120s a brain-ului, alte căi) putea lansa aplicații prin execSync cu argumente injectabile și putea muta/scrie fișiere neaprobat.

Corrections din src/security/host-action-policy.ts + src/mind/kelvin.ts + src/mind/automation/skills/app-launcher.ts:

  • minimumPolicy clasifică acum skill-urile de automatizare: app-launcher și note-taker → confirm; file-organizer non-dryRun → deny cu motivul exact-plan (legacyActionBlockReason); diagnosticul read-only (web-search, system-monitor, file-finder, daily-briefing, timer, reminder) rămâne allow.
  • Branch-ul de automatizare din mind.hear aplică hostSkillPolicy înainte să ruleze orice skill: deny → răspuns de refuz cu motivul; confirm → refuzat ca fir intern („canal de rezervă”, nimeni nu poate aproba) cu automationResult: 'blocked', persistat în memorie ca blocked. Skill necunoscut → eroare explicită.
  • Privilegiu minim: ctx.permissions este exact skillDef.permissions (nu lista completă a tuturor permisiunilor).
  • app-launcher hardenizat: isSafeLaunchToken respinge metacharacterii shell ($(){}!&|<>^% "',;) în appName și args înainte de execSync — căi/argumente nepotrivite nu ajung niciodată în shell.

Rădăcina documentelor (POST /api/documents/root în src/grove-api.ts) făcea mkdirSync(target, {recursive:true}) pe orice cale din request, apoi saveConfig + aplicare live — putea crea foldere oriunde și îndrepta indexatorul viu spre ele. resolveDocumentsRoot (src/documents/request-scope.ts) refuză acum crearea de foldere noi în afara PROJECT_ROOT (HTTP 400); selectarea unui folder existent rămâne permisă oriunde (folder administrat de proprietar — deploy-ul actual folosește C:\Users\alexm\Documents\KiwiDocs, care există și rămâne selectabil). </> de control și căile invalide primesc 400.

#Fix revizia 10 — brokerul /api/documents/organize

Inspecția suplimentară a găsit ruta motor POST /api/documents/organize care, cu dryRun=false, executa mutări reale de fișiere fără să treacă prin politica host-action (refuzul legacyActionBlockReason pentru documents_organize non-dryRun se aplica numai în registry.run). Ruta refuză acum orice dryRun !== true cu HTTP 403 și același mesaj ca registrul; web-ul nu apelează această rută. Ruta rămâne dryRun-only ca și skill-ul; mutările reale rămân în planul exact aprobat al P1.

#Defecte confirmate

Nouă teste noi au eșuat înaintea corecțiilor: modelul executa unelte în afara listei prezentate, inclusiv în streaming; lista goală nu dezactiva uneltele; allow al apelantului ocolea deny configurat; fallback-ul offline ignora politica și lista permisă; confirmarea uita politica inițială; mutațiile gazdei puteau folosi configurații vechi allow; previzualizarea tăia argumentele după 100 de caractere; registrul permitea încă operații largi în lot.

Un scenariu suplimentar verifică faptul că pașii generați ai rutinelor nu pot confirma o modificare anterioară. Inspecția Telegram direct a găsit /vm start și variante de acțiuni PC fără confirmare, /docs organize move executat direct și /dev pornind OpenCode după aprobarea generală a sarcinii.

#Comportament aplicat

src/security/host-action-policy.ts compune restricțiile: refuzul are prioritate, apoi confirmarea, apoi permisiunea. Politica apelantului poate restrânge accesul, fără a slăbi refuzul configurat sau politica obligatorie a gazdei. tools limitează execuția în buclele agentului JSON și streaming; [] nu prezintă și nu permite nicio unealtă.

Acțiunile cunoscute pentru programe, comenzi, scriere de fișiere/clipboard, alimentare, wake, modificări VM/procese, backup, programări și creare/ștergere de rutine cer confirmare chiar cu vechiul allow. Diagnosticul rămâne disponibil conform restricțiilor configurate. Este o listă explicită de unelte cunoscute; nu certifică pluginurile arbitrare ca fiind numai pentru citire.

ConversationManager aplică aceeași verificare fallback-ului offline și execuției în așteptare. Intrarea păstrează argumente copiate, identitatea originală a canalului, expirarea în cinci minute și politica inițială; la confirmare se reverifică politica inițială și cea curentă. Firele interne scheduler/routine/auto-resolve/documents refuză acțiunile care cer confirmare, fără a interpreta textul generat drept accept. Aprobările dispar în continuare la restart.

Previzualizarea generică serializează unealta și argumentele complete, identifică gazda operatorului și escapează caracterele de control, backtick și control direcțional. Peste 2.800 de caractere, răspunsul refuză explicit și nu creează aprobare; textul acceptat nu este trunchiat. Sunt prezentate argumentele cerute, nu fiecare efect ulterior al unui executabil. Diff-urile, căile/configurația rezolvate imuabil și hash-urile stării anterioare rămân în migrarea P1.

Apelurile Telegram directe către registru folosesc acum același helper de politică. Acesta copiază argumentele, cere butoanele private existente de unică folosință când este necesar și reverifică politica înainte de execuție. Refuzul explicit se aplică și diagnosticului direct. Nu s-au trimis mesaje Telegram live în teste.

#Acțiuni largi oprite până la planuri exacte

IntrareRezultat curentÎnlocuire
dev_task, Telegram /dev <sarcină>Refuz explicit; dev.enabled=true nu poate porni un jobModificări/comenzi revizuite prin sarcinile comune și codarea P2 (în P1)
documents_organizePlan exact pregătit pe dryRun:true (copii de recuperare); execuție doar cu exactPlan valid + confirmare (revizia 16)Implementat — folderele/deploy KiwiDocs încă nu au rulat planuri
disk_doctor cu action:reapNon-dryRun refuzat fără ref; plan_reap pregătește plan recoverabil; reap+exactPlan mută în backup (revizia 16)Implementat — nimic anterior de șters nu a fost șters
file-organizer (automatizarea mind)Preview-only din revizia 17: dry-run raportează țintele și nu atinge discul; non-dryRun refuzat cu motiv exact-planPlan exact file_organizer aprobat în chat, cu backup înaintea mutării (implementat — a rămas o singură cale de mutare: aprobarea operatorului)
file_organizer (skill builtin)dryRun:true pregătește un plan exact (allow); execuția doar cu exactPlan validat + aprobare (confirm), fără ref → deny (revizia 17)Implementat — niciun plan rulat în producție încă

Registrul blochează operațiile înainte de intrarea în unealtă; intrările builtin le refuză și ele. Ruta broker /api/documents/organize aplică acum același refuz (revizia 10), iar web-ul nu o cheamă. DevManager și biblioteca de documente rămân cod pentru migrare, fără apelant din aplicație care să pornească joburi DevManager. Testele cu subprocese sintetice verifică direct acel runner. dev_status și dev_cancel cu confirmare rămân disponibile pentru joburi vechi. Configurațiile existente și bibliotecile sunt păstrate.

#Dovezi și lucru rămas

  • tests/mind-automation-policy.test.ts (nou, revizia 14): clasificarea automatizărilor (app-launcher/note-taker → confirm; file-organizer non-dryRun → deny, dryRun → allow; web-search/system-monitor/file-finder → allow), motivul exact-plan al file-organizer, și refuzul metacharacterilor în app-launcher (appName și args) fără a apela execSync; dry-run rămâne permis.
  • tests/document-scope.test.ts (extins, revizia 14): resolveDocumentsRoot refuză calea lipsă și crearea în afara folderului proiectului, permite crearea în interior și selectarea unui folder existent oriunde.
  • Suita motor (revizia 14): 105 teste, 104 trecute + unul semantic opțional sărit (+4 față de revizia 13). Mind/web: 222 trecute în 33 suite (neschimbat — fără cod web). Typecheck motor trecut.
  • Suita motor (revizia 15): 113 teste, 112 trecute + unul semantic opțional sărit (+8 față de revizia 14, tests/plugin-capabilities.test.ts). Mind/web: 222 trecute în 33 suite; ambele typecheck-uri trecute.
  • Suita motor (revizia 16): 124 teste, 123 trecute + unul semantic opțional sărit (+11 față de revizia 15, tests/exact-plan.test.ts). Mind/web: 222 trecute în 33 suite (neschimbat); ambele typecheck-uri trecute.
  • tests/exact-plan.test.ts (nou, revizia 16): ciclul complet move și delete cu backup/rollback, invalidare la coliziune, eșec parțial, fluxurile de skill documents_organize și disk_doctor, politica deny→confirm și aprobarea reală prin manager.
  • tests/host-action-boundary.test.ts: 10 regresii trecute, cu contoare sintetice în locul mutațiilor reale pe gazdă; primele nouă au eșuat toate înainte de corecție.
  • tests/mind/unit/telegram-owner-channel.test.ts: 8 cazuri trecute cu transport simulat: aprobare VM/PC, refuz schimbat după propunere și blocarea mutărilor/codării largi.
  • Testele cu fișiere temporare reale cer accept înainte de emiterea artefactului notiței salvate; clasificarea documentelor păstrează sursele și refuză mutarea.

Rămân în P0 (revizia 17): dev_task rămâne oprit — config implicit deny, dev_status/dev_cancel fail-closed, DevManager.start fără apelant în producție; înlocuitorul sigur e fluxul de sarcini-dispozitiv P1 / codare interactivă P2, amânat în P1. Acceptarea pe două sisteme rămâne blocată (inexistent al doilea Windows; VM-urile sunt Linux). documents_organize, disk_doctor reap și file_organizer sunt acum permise numai cu plan exact + aprobare și nu au rulat mutări/ștergeri reale în producție. Motorul rămâne al operatorului; nu devine rezervă pentru dispozitivele utilizatorilor web. Apelurile directe de nivel jos din codul de încredere nu sunt API public de autorizare.

Surse: src/exactions/ledger.ts (nou), src/skills/fileOrganize.ts (nou), src/skills/builtin/fileOrganizer.ts (nou), src/agent/agent.ts, src/conversation/manager.ts, src/skills/registry.ts, src/skills/builtin/{documents,diskDoctor}.ts, src/security/host-action-policy.ts, src/mind/kelvin.ts, src/telegram/run.ts, contractul stării, checkpoint.

~ End of Document ~