@soulbatical/stella
v1.8.20
Published
Learning Agent for MCP servers - knowledge base, reflections, bug reports, chat engine
Maintainers
Readme
Stella - Learning Agent for MCP Servers
Ralph bouwt, Stella navigeert — Stella beheert kennis, reflectie, bugreports en permissions voor MCP servers.
Stella & Ralph: Two Repos, One System
Stella and Ralph Manager are separate repositories with a deliberate architectural relationship:
| | Stella (@soulbatical/stella) | Ralph Manager |
|--|--------------------------|---------------|
| Type | NPM library (TypeScript package) | Full-stack application |
| Role | Middleware layer — gates, permissions, knowledge, reflections | Platform layer — dashboard, Telegram bot, autonomous loops, agents |
| Imported by | All MCP servers (web-mcp, snelstart-mcp, agentrook, sparkbuddy-live, ralph-manager, etc.) | Nothing — it's the top-level platform |
| Database | Uses Ralph Manager's Supabase (all stella_* tables) | Owns the Supabase project |
| Approval flow | Requests approvals via Telegram | Hosts the Telegram bot that handles approvals |
Why separate repos?
- Blast radius — A Stella change affects all consumers. Separate repos force conscious builds and testing per consumer.
- Versioning — Each consumer decides when to update Stella. No accidental HEAD changes.
- Focus — Stella = middleware concerns. Ralph Manager = platform/operational concerns.
The relationship is like Stripe SDK vs Stripe Dashboard: Stella is the library every project imports. Ralph Manager is the platform that hosts the infrastructure Stella depends on (database, approval flow, monitoring). Other consumers (agentrook, web-mcp, etc.) are pure consumers of the library — they use Stella but don't host its infrastructure.
Changelog
2026-03-17: session_task — verplichte taakbeschrijving bij identity confirmation
Probleem: Bij sessiestart bevestigde de LLM wie hij was (identity), maar niet wat hij ging doen. Hierdoor was er geen expliciete internalisering van de taak, en was achteraf niet zichtbaar welke taak een sessie had.
Oplossing: stella_confirm_identity en stella_session_init vereisen nu een session_task parameter (min 10 tekens) naast de bestaande summary.
Wijzigingen:
- Tool definition (
reflections.ts) —session_tasktoegevoegd als required field inconfirmIdentityDef - Handler (
reflections.ts) —handleConfirmIdentityvalideertsession_task(min 10 tekens), retourneert het in de response - Session init (
howtos.ts) —sessionInitDefschema en handler uitgebreid metsession_task, blokkeert identity confirmation zonder - Identity gate (
reflections.ts) — gate prompt vraagt nu om identity + taak met voorbeeld - Akkoord response (
howtos.ts) — hint na akkoord toontsession_taskin het voorbeeld - Stella hook (
~/.claude/hooks/stella-hook.sh) — stap 2 vermeldt expliciet datsummaryensession_taskbeide verplicht zijn
Voorbeeld:
stella_confirm_identity
summary="I am the Practical Executor. I focus on efficiency..."
session_task="Fix the 401 authentication error in the live widget"2026-02-28: Predictors — time-windowed tool health monitoring + Telegram alerting
Probleem: Stille tool failures bleven weken ongedetecteerd. cost_summary retourneerde 6+ weken lang lege resultaten (200 OK, geen error) door een niet-bestaande kolom (project_name i.p.v. project_id join). Geen enkel monitoring systeem pikte dit op.
Oplossing: Outcome-gebaseerde tool health monitoring via de neural predictors:
trainPredictors()— Analyseert howto_usage scores per tool met tijdsvensters (24h, 7d, 30d, all-time). Berekent per tool:- Success rate per venster (score >= 4 = success)
- Trend:
improving|declining|stable|insufficient_data - Lift boven/onder baseline (overall success rate)
- Confidence gewogen op basis van lift × sample size
Stale edge cleanup — Oude
predictsedges worden verwijderd vóór elke train-run, zodat tools die terug naar baseline gaan niet als "failure" blijven staan.Periodieke training — Daemon traint elke 6 uur (+ 30s na startup). Geen handmatige actie nodig.
Telegram alerting — Bij declining tools met >10% drop en >=3 recente samples stuurt de daemon een alert via
POST /api/internal/notifications/agentnaar ralph-manager → Telegram. 24h cooldown per tool voorkomt spam.
Voorbeeld alert:
⚠️ Tool Health Alert
📉 stella_plan_update declining
All-time: 73% (n=26)
7d: 50% (n=4)
Drop: -23ppRoot cause cost_summary: scripts.ts en staff-reports.ts selecteerden project_name van ralph_sessions, maar die kolom bestaat niet — de tabel heeft project_id (UUID FK). Fix: .select("project:projects(name), ...") join via FK relatie.
2026-02-23: Gate blokkade-tekst herschreven voor betere AI compliance
Probleem: Na elke MCP server reload reset akkoordGiven naar false (in-memory boolean). Wanneer de AI daarna een tool aanroept (bijv. task_list), retourneert de gate een blokkade-response. De AI interpreteerde dit consequent als "de tool gaf lege resultaten" of "de tools zijn kapot" en ging debuggen/omheen werken in plaats van gewoon stella_howto_akkoord te callen. Dit patroon herhaalde zich sessie na sessie.
Root cause: De blokkade-tekst stond ONDERAAN een muur van basis-instructies, bugs en identity context (~100+ regels). De AI las die muur als "informatie" en miste de actie-verplichting. Bovendien was niet expliciet dat de tool NIETS retourneerde — de AI dacht dat het resultaat van de oorspronkelijke tool was.
Fix 1 — gates.ts blokkade-tekst herschreven:
- Blokkade staat nu BOVENAAN (vóór basis-instructies, niet erna)
⛔ TOOL CALL BLOCKED — "{toolName}" returned NOTHING— meteen duidelijk"The result you see is NOT from {toolName}"— voorkomt misinterpretatie"This happens after every MCP reload"— verklaart waarom het opnieuw moet- Drie expliciete anti-patronen: "DO NOT work around this", "DO NOT interpret as empty results", "DO NOT debug why tools seem broken"
Fix 2 — stella-gate.sh hook tekst uitgebreid:
- Na de 3 onboarding-stappen staat nu een
⚠️ BELANGRIJKblok - Legt preventief uit dat "TOOL CALL BLOCKED" NIET betekent dat de tool kapot is
- Instrueert om NOOIT te debuggen maar gewoon de 3 stappen te doen
- Geldt bij ELKE sessiestart EN na elke MCP server reload
2026-02-21: Cross-scope howto editing fix (findHowtoForEdit)
Probleem: howto_upsert, howto_patch, howto_delete gebruikten strict scope matching (eq(scope_id)), terwijl howto_get, howto_rate, howto_publish cross-scope lookup gebruikten (applyScopeFilter + maybeSingle()). Dit veroorzaakte drie bugs:
Patch/upsert konden publieke howto's niet vinden vanuit een project scope. Een publieke howto (scope_id: null) was onvindbaar vanuit bijv. sparkbuddy-live scope.
howto_patchgaf "not found in current scope",howto_upsertmaakte een scoped duplicate aan i.p.v. de bestaande te updaten.Duplicates veroorzaakten crashes in alle cross-scope handlers. Zodra er twee rijen bestonden met dezelfde slug (public + scoped), faalde
maybeSingle()met "multiple rows returned" — waardoorhowto_get,howto_rateenhowto_publishook kapot gingen.Delete/publish cyclus.
howto_deletevond alleen de scoped versie (strict match), niet de publieke. Na delete probeerde de agent opnieuw upsert → nieuwe scoped duplicate → zelfde deadlock.
Fix: Nieuwe helper findHowtoForEdit(slug, columns) vervangt alle 7 lookup-patronen:
// Oud: 2 incompatibele patronen
// Pattern A (upsert/patch/delete): eq("scope_id", scopeId) → mist publieke howtos
// Pattern B (get/rate/publish): applyScopeFilter() + maybeSingle() → crasht bij duplicates
// Nieuw: 1 consistent patroon
const { data, error } = await findHowtoForEdit(slug);
// → Fetcht ALLE matches (own scope + public/null)
// → Disambigueert: prefereert eigen scope > public (null) > eerste match
// → Geen maybeSingle() meer → nooit crash bij duplicatesAangepaste handlers: handleHowtoGet, handleHowtoUpsert, handleHowtoPatch, handleHowtoRate, handleHowtoPublish, handleHowtoDelete, processInlineReview.
2026-02-21: Duplicate howto prevention bij upsert
Probleem: Agents maakten regelmatig howto's aan die qua inhoud sterk overlappen met bestaande howto's. Voorbeeld: systematic-browser-crud-testing werd aangemaakt terwijl saasaway-client-test-script al hetzelfde onderwerp dekte. Dit leidde tot fragmentatie in de kennisbank.
Fix: stella_howto_upsert doet nu een embedding similarity check bij het aanmaken van een nieuwe howto (slug bestaat nog niet). Als er bestaande howto's zijn met >= 50% similarity, wordt de creatie geblokkeerd:
Similar howtos already exist:
- "saasaway-client-test-script" (72% similar ★4, 2x) — Complete test procedure...
Options:
1. Retry with force=true to create anyway
2. Use stella_howto_patch slug="..." to add content to existing
3. Use stella_howto_upsert slug="..." to fully rewrite existing- Alleen bij nieuwe slugs — updates van bestaande howto's worden niet gecheckt (dat is bewust herschrijven)
- Bypass:
force=trueparameter om toch aan te maken na review van de matches - Geen extra stappen bij geen matches — check is transparant en voegt geen latentie toe als er geen duplicaten zijn
2026-02-21: Gate hook optimalisatie + howto kwaliteitsverbeteringen
Probleem 1: Gate hook injecteerde volledige instructies bij ELKE prompt. 130+ regels context werd bij elk user prompt herhaald — enorme token-verspilling.
Fix: State file per CLI sessie (~/.claude/state/stella-gate-$PPID). Eerste prompt krijgt volledige instructie, daarna niets. Stale state files worden automatisch opgeruimd (PID check). Reflection keys worden ook maar 1x geïnjecteerd (eerste keer dat ze beschikbaar zijn).
Probleem 2: Howto search te breed/plat. Agents zochten op het symptoom ("agentrook 401 authentication widget chat API") zonder werktype of context mee te geven.
Fix: Zoektermen moeten nu drie dimensies combineren:
- ONDERWERP — wat is het? (bijv. "401", "RLS")
- WERKTYPE — wat ga je doen? (bijv. "debug", "deploy", "refactor")
- CONTEXT — welke stack/laag? (bijv. "express", "supabase", "doppler")
Probleem 3: Bij geen howto match ging agent blind bouwen. Geen kennis verzamelen, direct aan het werk.
Fix: Stap 1b instrueert nu expliciet om EERST kennis te verzamelen:
- Optie A: eigen kennis (Grep/Read bestaande code)
- Optie B: externe kennis (Context7, WebSearch, skills)
- Optie C: combinatie (vaak het beste)
Probleem 4: Howto's werden te context-specifiek geschreven. "Fix 401 in agentrook widget" i.p.v. "Debug Express authentication errors".
Fix:
- Gate hook bevat nu stap 1c met generiek-schrijven regels + bad/good voorbeelden
- Upsert tool description bevat zelfde instructie als extra reminder
is_publicdefault gewijzigd vanfalsenaartrue— howto's zijn standaard publiek
Probleem 5: Agent begon inhoudelijk te antwoorden zonder onboarding. Bij "hoi, maak een advertentie" ging de agent vragen stellen zonder eerst howto's te laden.
Fix: Gate hook bevat nu een hard blok "EERSTE ACTIE — voordat je IETS anders doet":
- stella_howto_akkoord
- stella_confirm_identity
- stella_howto_search Pas NA stap 3 mag inhoudelijk geantwoord worden.
2026-02-21: Akkoord gate enforcement hersteld
Probleem: Op 19 feb werd de daemon geïntroduceerd met setAkkoordForPid in de hook. De hook signaleerde automatisch akkoord aan de daemon bij elk user prompt, waardoor de MCP akkoord gate altijd overgeslagen werd. Hierdoor was de hele learning workflow (howto search → plan → execute → summarize → rate) niet meer afgedwongen — Claude kon direct tools gebruiken zonder de Stella flow te doorlopen.
Oorzaak: De hook deed setAkkoordForPid → daemon zette akkoordGiven=true → MCP's checkten isAkkoordConfirmedShared() → true → gate overgeslagen. Zonder akkoord gate werd er geen howto geladen, zonder howto geen plan gate, en zonder plan gate geen enforcement.
Fix:
stella-gate.sh:setAkkoordForPidvervangen doorregisterCliPid— registreert alleen de PID, geeft geen akkoorddaemon.ts: nieuwregisterCliPidmethod toegevoegd (lightweight, geen akkoord)stella-howto-hooks.sh: logging verplaatst naar~/.claude/logs/stella/hooks-YYYY-MM-DD.log(dagelijkse rotatie, alleen matches gelogd)- Daemon stderr gaat nu naar
~/.claude/logs/stella/daemon-YYYY-MM-DD.log(was/dev/null)
Resultaat: De akkoord gate blokkeert weer hard. Claude moet stella_howto_akkoord callen om tools te unlocken, wat de volledige learning workflow afdwingt.
Documentation
| Document | Beschrijving | |----------|-------------| | docs/RUVECTOR.md | RuVector local store: architectuur, daemon protocol, neural graph, data layout, troubleshooting | | docs/PLAN-neural-graph.md | Neural Graph bouwplan (M1-M4): schema's, channel keys, co-activatie, policy engine. Status: gebouwd | | docs/SECURITY.md | Security model: HMAC-signed tokens, network isolation, threat model | | docs/PRD-stella-store-ruvector.md | Origineel ontwerp StellaStore + RuVector dual-write (historisch) |
Features
- How-To Knowledge Base — Semantische zoekfunctie (pgvector embeddings), versioning, scoring. Basis how-to's laden alleen de eigen scope (~800 tokens i.p.v. ~5000)
- Plans & Summaries — Gestructureerd plan vooraf, samenvatting achteraf met semantic search op eerdere samenvattingen
- Stella Daemon — Centraal state-proces via Unix socket. Alle MCP servers delen onboarding, plan en howto state real-time
- Hook-based Onboarding —
stella-gate.shUserPromptSubmit hook levert instructies (alleen eerste prompt, daarna niets), start daemon en registreert CLI PID. Instrueert agent om onboarding (akkoord → identity → search) te doen als EERSTE ACTIE voordat inhoudelijk geantwoord wordt - Reflection System — Identiteits-gebaseerde reflecties met 10 lagen + empathy questions
- Bug Reports — Gecentraliseerd in ralph-manager MCP (andere MCPs verwijzen hiernaar via gates)
- Neural Graph — Adaptief lerend netwerk (Hebbian co-activatie, bundle detectie, policy engine). Zie docs/RUVECTOR.md voor operationele details en docs/PLAN-neural-graph.md voor het architectuurplan
- Tool Health Monitoring — Outcome-gebaseerde predictors detecteren stille tool failures via tijdsvensters (24h/7d/30d). Daemon traint elke 6 uur en stuurt Telegram alerts bij declining trends. Detecteert problemen die traditionele health checks missen (tools die 200 OK retourneren maar slechte resultaten opleveren)
- AI User Sentiment — PostToolUse hook analyseert het laatste user bericht met Claude Haiku voor sentiment (frustrated/urgent/positive/confused/correcting/neutral) + 7 indices. Fallback naar grep-heuristics als API onbeschikbaar
- Gate Middleware — Enforceert learning workflows (akkoord, evaluatie, summary, plan, identiteit, reflectie, project context)
- Project Context Gate — Write tools geblokkeerd tot project geselecteerd, deploy_config auto-update
- Howto Hooks — How-to's kunnen Claude Code lifecycle hooks bevatten (PreToolUse, PostToolUse, Stop, etc.) die automatisch actief worden bij
howto_geten verwijderd bijhowto_rate - Inline Parameters —
_reflection,_review,_statusvoor efficiente roundtrips - Permission System — Universeel toegangsbeleid per tool (
free,confirm,approve,block) - Agent Profiles — Per-sessie permission overrides via geheime codes
Architecture
┌──────────────────────────────────────────────────────────────┐
│ Claude Code Session │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ UserPromptSubmit Hook (stella-gate.sh) │ │
│ │ - Levert instructies (alleen 1e prompt, daarna niets) │ │
│ │ - Start daemon als die er niet is │ │
│ │ - Registreert CLI PID (geen auto-akkoord) │ │
│ │ - Injecteert reflection keys (1x, zodra beschikbaar) │ │
│ └────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Pre/PostToolUse + Stop Hook (stella-howto-hooks.sh) │ │
│ │ - Query daemon voor actieve howto hooks │ │
│ │ - Voert command/prompt/agent hooks uit │ │
│ │ - Output via JSON (systemMessage + additionalContext) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │snelstart │ │agentrook │ │ stella- │ │ ralph- │ │
│ │ MCP │ │ MCP │ │ proxy │ │ manager │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ └──────────────┼──────────────┼──────────────┘ │
│ │ │ │
│ ┌───────▼──────────────▼───────┐ │
│ │ Stella Daemon Process │ │
│ │ /tmp/stella-daemon.sock │ │
│ │ │ │
│ │ Shared State (in-memory): │ │
│ │ - akkoordGiven │ │
│ │ - identityConfirmed │ │
│ │ - activePlan │ │
│ │ - openSessions │ │
│ │ - activeHooks │ │
│ │ - stepCounter │ │
│ │ - reflectionKeys │ │
│ │ - neuralPolicy (STATE_OUT) │ │
│ │ │ │
│ │ RuVectorStore (embedded): │ │
│ │ - EmbeddingCache (cosine) │ │
│ │ - MetadataStore (JSON/disk) │ │
│ │ - Neural Graph (nodes/edges) │ │
│ │ - PolicyEngine (Thompson) │ │
│ │ - Decay Runner (10 min) │ │
│ │ - Predictor Trainer (6 hr) │ │
│ │ → Telegram alerts │ │
│ └───────────────────────────────┘ │
│ │
│ ┌───────────────────────────────┐ │
│ │ Supabase │ │
│ │ howtos, plans, reflections, │ │
│ │ permissions, episodes │ │
│ └───────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘Installation
Optie 1: npm install from local path (aanbevolen)
npm install ../stellaOptie 2: npm link (lokaal development)
# In stella project:
cd ~/projecten/stella
npm run build
npm link
# In je MCP project:
cd ~/projecten/web-mcp
npm link @soulbatical/stellaOptie 3: npm install from git
npm install git+https://github.com/ralph-group/stella.gitEnvironment Variables
Voeg toe aan je .env:
# Ralph Manager Supabase (gedeeld tussen alle projecten)
LEARNING_SUPABASE_URL=https://xxx.supabase.co
LEARNING_SUPABASE_SERVICE_KEY=eyJ...
# Project scope (uniek per project)
LEARNING_SCOPE_ID=web-mcp # of 'sparkbuddy', 'snelstart', etc.
# OpenAI voor semantic search
OPENAI_API_KEY=sk-...Quick Start
Met withGates() (aanbevolen)
import {
withGates,
initDaemonClient,
configurePermissions,
configureStellaClient,
decorateToolDefinitions,
} from '@soulbatical/stella';
// Bij startup:
configureStellaClient(supabase, 'web-mcp');
configurePermissions({
scopeId: 'web-mcp',
approvalService: 'web-mcp',
approvalUrl: 'http://localhost:3005',
});
// Initialize daemon connection (auto-starts daemon if not running)
await initDaemonClient('web-mcp');
// ListTools handler:
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools: await decorateToolDefinitions(myToolDefs) };
});
// CallTool handler:
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
return withGates(name, args, async () => {
return myHandler(args);
});
});withGates() handelt automatisch af:
- Inline parameter processing (
_reflection,_review,_status) - Gate checks (akkoord, evaluatie, summary, plan, identiteit, reflectie, project context)
- Permission checks (free/confirm/approve/block)
- Tool execution met error handling
- Auto-update deploy_config (na succesvolle write tools)
- Post-enrichment (tips, reflection prompts, batch suggestions)
decorateToolDefinitions() doet twee dingen:
- Permission prefixes —
[CONFIRM],[APPROVE],[BLOCKED]in tool descriptions - Inline parameter schemas — Injecteert
_reflectionin deinputSchema.propertiesvan elke tool, zodat LLMs het als formele parameter zien en inline gebruiken
Handmatig (voor custom flows)
import {
checkGates,
processInlineParams,
getPostEnrichment,
checkPermission,
requestApproval,
} from '@soulbatical/stella';
const inlineResult = await processInlineParams(name, args);
if (inlineResult) return inlineResult;
const gate = await checkGates(name);
if (gate) return gate;
// Permission check
const perm = await checkPermission(name, args);
if (perm.blocked) return errorResponse('Tool blocked');
if (perm.level === 'approve') {
const approval = await requestApproval(name, args);
if (!approval.approved) return errorResponse(approval.message);
}
const result = await handler(args);
const { additions, blocked } = await getPostEnrichment(name, result);
if (blocked) return blocked;
result.content.push(...additions);Hook Setup (stella-gate.sh)
De hook is de eerste stap in elke sessie. Hij draait als UserPromptSubmit hook in Claude Code bij elk user prompt.
Installatie
# Symlink (bron = stella repo, actief via Claude Code hooks)
ln -s ~/projecten/stella/hooks/stella-gate.sh ~/.claude/hooks/stella-gate.shWat doet de hook?
- Levert instructies (alleen eerste prompt) — stack, naming, env, ports, onboarding flow, howto-schrijfregels. Gebruikt state file (
~/.claude/state/stella-gate-$PPID) om herhaling te voorkomen. Stale state files worden automatisch opgeruimd (PID check). - Start daemon als die er niet is (stderr →
~/.claude/logs/stella/daemon-YYYY-MM-DD.log) - Registreert CLI PID via
registerCliPid— daemon kent de sessie maar geeft geen akkoord (akkoord moet via MCP gatestella_howto_akkoord) - Injecteert reflection keys (1x) — query
getReflectionKeysForPid, inject bij eerste keer dat ze beschikbaar zijn (na MCP loaded identity), daarna nooit meer
Howto Hooks Dispatcher (stella-howto-hooks.sh)
How-to's kunnen Claude Code lifecycle hooks bevatten. Wanneer een howto geladen wordt via stella_howto_get, worden de hooks opgeslagen in de daemon. De dispatcher (stella-howto-hooks.sh) draait bij elk PreToolUse, PostToolUse en Stop event.
Installatie
ln -s ~/projecten/stella/hooks/stella-howto-hooks.sh ~/.claude/hooks/stella-howto-hooks.shRegistreer in ~/.claude/settings.json onder hooks:
"PreToolUse": [{ "hooks": [{ "type": "command", "command": "~/.claude/hooks/stella-howto-hooks.sh", "timeout": 30 }] }],
"PostToolUse": [{ "hooks": [{ "type": "command", "command": "~/.claude/hooks/stella-howto-hooks.sh", "timeout": 30 }] }],
"Stop": [{ "hooks": [{ "type": "command", "command": "~/.claude/hooks/stella-howto-hooks.sh", "timeout": 30 }] }]Flow
howto_get → hooks uit DB → daemon.setActiveHooks(slug, hooks, cwd)
↓
Claude Code event (PreToolUse/PostToolUse/Stop)
→ stella-howto-hooks.sh
→ leest hook_event_name + cwd uit stdin JSON
→ skip als stop_hook_active=true (voorkomt infinite loops)
→ query daemon: getActiveHooksForEvent(cliPid, event, cwd)
→ daemon retourneert hooks met cwd per hook
→ script filtert: skip hook als hook.cwd niet matcht met sessie cwd
→ voert command hooks uit, stuurt prompt/agent hooks als context
→ JSON output: { systemMessage, additionalContext }
↓
howto_rate → daemon.removeActiveHooks(slug) → hooks wegcwd Scoping
Hooks worden alleen uitgevoerd in de juiste project-sessie. Dit voorkomt dat hooks geladen in ~/projecten/stella vuren in een sessie die in ~/projecten/web-mcp werkt.
Hoe het werkt:
- Bij
setActiveHooksslaat de daemon decwdop per hook set - Bij
getActiveHooksForEventstuurt de daemon decwdmee per hook - Het hook script vergelijkt de
cwduit Claude Code stdin methook.cwd - Fallback: als geen explicit
cwd, parse het uitcd <pad>in het command
Stop hook specifiek: stop_hook_active wordt gecheckt om infinite loops te voorkomen — als Claude al in een stop-hook continuation zit, wordt de hook overgeslagen.
Hook types in howto's
{
"hooks": {
"PostToolUse": [{
"type": "command",
"command": "npx tsc --noEmit",
"description": "TypeScript check na elke edit",
"matcher": "Edit|Write"
}],
"Stop": [{
"type": "prompt",
"command": "Check of alle bestanden opgeslagen zijn",
"description": "Save reminder"
}]
}
}| Type | Werking |
|------|---------|
| command | Shell command uitvoeren, stdout als context |
| prompt | Tekst als context naar het model sturen |
| agent | Subagent instructie als context sturen |
Output
Het script output JSON zodat Claude Code het correct verwerkt:
systemMessage— zichtbaar voor de gebruiker in de CLIadditionalContext— zichtbaar voor het model
PID Resolution
MCP servers draaien via doppler run -- node, dus hun process.ppid = doppler PID, niet Claude CLI PID. Dit creëert twee gescheiden PID chains:
Hook: $PPID = Claude CLI PID (40973) → registerCliPid(40973) → state aangemaakt (geen akkoord)
MCP server: process.ppid = doppler PID (41105) → register(41105) →
getOrCreateState(41105) → akkoordGiven=false → gate blokkeert
LLM callt stella_howto_akkoord → setAkkoordGiven() → broadcast naar alle clientsAkkoord: gaat via stella_howto_akkoord tool call (hard enforcement). De setAkkoordForPid method bestaat nog voor backward compat maar wordt niet meer door de hook gecalld.
Active Hooks: ancestor walk faalt als doppler reparents naar ppid=1. Oplossing: getActiveHooksForEvent scant alle cliStates globaal en filtert op cwd (project directory) in plaats van PID matching. Het hook script vergelijkt de CLI sessie cwd met de hook's project cwd.
Stella Daemon (Cross-MCP Shared State)
In een Claude Code sessie draaien meerdere MCP servers tegelijk. Zonder sharing zou elke MCP de volledige onboarding afdwingen en plan/howto state niet kunnen zien — dit veroorzaakte deadlocks (bijv. howto_get op adgency + plan_create op stella-proxy → plan gate permanent geblokkeerd).
Architectuur
De daemon is een detached Node.js process dat communiceert via JSON-RPC 2.0 over Unix domain socket (/tmp/stella-daemon.sock).
MCP Server A ──┐
MCP Server B ──┼──► Unix Socket ──► Stella Daemon (in-memory state)
MCP Server C ──┘ │
└──► Broadcast state changes to all clientsDrie componenten:
| Component | Bestand | Rol |
|-----------|---------|-----|
| daemon.ts | Server process | Houdt state in geheugen, handelt RPC requests af, broadcast wijzigingen |
| daemon-client.ts | Client library | Verbindt met daemon, local cache + broadcast sync |
| daemon-spawn.ts | Auto-start | Controleert of daemon draait, start indien nodig, cleanup stale PID/socket |
Hoe het werkt
- Eerste MCP server start →
initDaemonClient(scopeId)→ensureDaemon()fork't daemon process - Client registreert →
registerRPC → ontvangt huidige state snapshot → local cache gesync - State wijzigt → client stuurt RPC (bijv.
setAkkoordGiven) → daemon update + broadcast naar alle clients - Broadcasts →
stateChangednotifications updaten de local cache van elke client real-time - State lezen → local cache (geen RPC nodig, instant)
Gedeelde State
| State | Daemon method | Beschrijving |
|-------|--------------|-------------|
| Akkoord | setAkkoordGiven / isAkkoordGiven | Basis instructies bevestigd |
| CLI PID (hook) | registerCliPid / getReflectionKeysForPid | Hook registreert PID (geen akkoord) + query keys per CLI PID |
| Identity | setIdentityConfirmed / isIdentityConfirmed | Identiteit bevestigd (impliceert akkoord) |
| Plan | setActivePlan / getActivePlan | Actief plan met stappen |
| Summary | setSummaryRequired / isSummaryRequired | Summary vereist na plan completion |
| Howto Sessions | setHowtoSession / removeHowtoSession | Open howto sessies |
| Active Hooks | setActiveHooks / removeActiveHooks / getActiveHooksForEvent | Howto lifecycle hooks (per event, global scan + cwd filter) |
| Step Counter | incrementStepCounter / getStepCounter | Totaal tool calls (voor evaluatie gate) |
| Reflection | setPendingReflection | Reflectie vereist |
| Reflection Keys | setReflectionKeys / getReflectionKeysForPid | Reflection question keys (pushed door MCP, queried door hook) |
| Bug/Howto Actions | setPendingBugAction / setPendingHowtoAction | Actie gates |
| Basis Loaded | setBasisLoaded / isBasisLoaded | Basis howtos geladen |
Cross-MCP Gate Resolution
| Gate | Logica |
|------|--------|
| Akkoord | localState.akkoordGiven (gesync via daemon) → 1x akkoord geldt voor alle MCP's |
| Identity | localState.identityConfirmed → 1x identity geldt voor alle MCP's |
| Plan | localState.activePlan → plan aangemaakt in 1 MCP is zichtbaar in alle MCP's |
| Summary | localState.summaryRequired → blokkeert howto_rate in alle MCP's |
| Howto sessions | localState.openSessions → sessie geopend in 1 MCP is zichtbaar overal |
Flow bij meerdere MCP servers
User prompt (1e prompt):
→ stella-gate.sh hook draait
→ Levert volledige instructies (onboarding, stack, naming, howto-schrijfregels)
→ Start daemon als die er niet is (stderr → ~/.claude/logs/stella/)
→ registerCliPid(cliPid) → daemon kent de sessie (GEEN akkoord)
→ Maakt state file aan (~/.claude/state/stella-gate-$PPID)
User prompt (2e+):
→ stella-gate.sh hook draait
→ State file bestaat → geen instructies output
→ registerCliPid(cliPid)
→ Reflection keys: inject 1x zodra beschikbaar, daarna nooit meer
1e MCP tool call (bijv. op snelstart-mcp):
→ initDaemonClient() → daemon al gestart door hook
→ register(dopplerPid) → getOrCreateState() → akkoordGiven=false
→ checkGates() → 🔒 TOOLS BLOCKED → LLM moet stella_howto_akkoord callen
→ stella_howto_akkoord → setAkkoordGiven → broadcast naar alle clients
2e MCP server (bijv. agentrook-mcp):
→ initDaemonClient() → verbindt met bestaande daemon
→ register → ontvangt state met akkoordGiven=true (via broadcast)
→ howto_get → opent sessie → plan gate activeert
3e MCP server (bijv. stella-proxy):
→ plan_create → broadcast naar alle clients
→ agentrook ziet plan via local cache → plan gate passeert (geen deadlock)Technische details
- Protocol: JSON-RPC 2.0, newline-delimited, over Unix domain socket
- Socket:
/tmp/stella-daemon.sock - PID file:
/tmp/stella-daemon.pid - Auto-start: Eerste
initDaemonClient()call start daemon viafork()(detached) - Predictor training: Elke 6 uur (+ 30s na startup) traint
trainPredictors(). Bij declining tools (>10% drop, >=3 samples in 7d) stuurt alert via ralph-manager Telegram endpoint. 24h cooldown per tool - Auto-exit: 5 minuten zonder connecties → daemon stopt zichzelf
- Stale cleanup:
daemon-spawn.tsvalideert PID file tegen levend process, ruimt stale socket/pid op - Retry: Exponential backoff (50ms, 100ms, 200ms, 400ms) bij wachten op daemon startup
- RPC timeout: 5 seconden per call
- Fallback: Als daemon onbereikbaar → local-only mode (zelfde gedrag als voor daemon bestond)
- Backward compat:
getSharedSession()retourneert session-achtig object voor code die de oude file-based API gebruikte
Logging
Alle lokale Stella logs staan in ~/.claude/logs/stella/ (dagelijkse rotatie):
| Bestand | Wie schrijft | Wat |
|---------|-------------|-----|
| daemon-YYYY-MM-DD.log | stella-gate.sh (daemon stderr) | Registraties, akkoord events, hook changes, errors |
| hooks-YYYY-MM-DD.log | stella-howto-hooks.sh | Alleen gefirde hooks (lege results worden niet gelogd) |
MCP tool call data (episodes, token usage, plans, summaries) gaat naar Supabase.
Diagnostics
# Check of daemon draait
ls -la /tmp/stella-daemon.sock
cat /tmp/stella-daemon.pid
ps aux | grep stella.*daemon
# Ping daemon
echo '{"jsonrpc":"2.0","method":"ping","params":{},"id":1}' | nc -U /tmp/stella-daemon.sock
# Get full state (check akkoordGiven, activePlan, etc.)
echo '{"jsonrpc":"2.0","method":"getState","params":{},"id":1}' | nc -U /tmp/stella-daemon.sock
# Get reflection keys for a CLI PID
printf '{"jsonrpc":"2.0","id":1,"method":"getReflectionKeysForPid","params":{"cliPid":"%s"}}\n' $PPID \
| nc -U /tmp/stella-daemon.sock -w1
# Bekijk daemon logs van vandaag
cat ~/.claude/logs/stella/daemon-$(date +%Y-%m-%d).log
# Bekijk hook fires van vandaag
cat ~/.claude/logs/stella/hooks-$(date +%Y-%m-%d).log
# Kill daemon (wordt automatisch herstart door hook of MCP call)
kill $(cat /tmp/stella-daemon.pid)Permission System
Levels
| Level | Gedrag | Menselijke actie |
|-------|--------|------------------|
| free | Direct uitvoeren | Geen |
| confirm | LLM moet bevestigen (confirmed=true) | Geen |
| approve | Telegram goedkeuring vereist | Klik op knop |
| block | Tool geblokkeerd | - |
Resolutie-volgorde
1. Agent profiel override (als profiel actief + tool match)
2. Supabase stella_tool_permissions tabel
3. Local overrides (hardcoded fallback via configurePermissions)
4. Default: 'free'Tool Description Decoratie
Bij ListTools worden descriptions automatisch verrijkt:
[CONFIRM] Boek inkoopfactuur...
[APPROVE — Telegram] DNS record toevoegen...
[BLOCKED] Gevaarlijke actie...Agent Profiles
Een agent profiel is een set permission overrides gekoppeld aan een geheime code:
// Agent stuurt code mee bij akkoord of via unlockAgentProfile()
const result = await unlockAgentProfile('mijn-geheime-code');
// → Laadt profiel permissions voor de rest van de sessieTijdelijke Permissions
Permissions kunnen tijdelijk ingesteld worden met een expires_at timestamp. Na afloop valt de tool automatisch terug naar het volgende niveau in de resolutie-volgorde.
-- Via Ralph Manager MCP:
permission_set scope_id="agentrook" tool_name="*" level="free" duration_hours=4
-- → Na 4 uur verloopt de override en gelden weer de defaultsExpired rows worden genegeerd bij resolutie — geen cleanup nodig.
Database Tabellen
-- Standaard permission levels per scope
stella_tool_permissions (scope_id, tool_name, level, confirm_message, approve_label, expires_at)
-- expires_at: NULL = permanent, timestamptz = auto-expire
-- Agent profielen
stella_agent_profiles (name, secret_code, scope_id, is_active)
-- Per-profiel permission overrides
stella_agent_profile_permissions (profile_id, scope_id, tool_name, level)
-- Audit trail
stella_permission_audit (scope_id, tool_name, level, agent_profile, result, ...)Migratie: ralph-manager/supabase/migrations/20260207_stella_permissions.sql
Inline Parameters
LLMs kunnen reflections, reviews en status updates meesturen bij reguliere tool calls.
Dit bespaart 2 roundtrips per reflectie (geen apart reflect → tool opnieuw).
Belangrijk: _reflection wordt automatisch geïnjecteerd in de inputSchema van elke tool via decorateToolDefinitions(). LLMs zien het als formele parameter en gebruiken het inline — geen aparte reflect-calls meer nodig.
// Reflectie meesturen (AANBEVOLEN — bespaart 2 roundtrips):
tool_name(..., _reflection: {
answers: [{key: "safety", score: 4, text: "..."}, ...],
last_step: "wat ik net deed",
next_step: "wat ik ga doen"
})
// Review meesturen:
tool_name(..., _review: {
slug: "mijn-howto",
score: 4,
outcome: "success",
review: "werkte goed"
})
// Status meesturen:
tool_name(..., _status: {
slug: "mijn-howto",
status: "in_progress",
remaining_steps: 5
})Reflection Prompt Optimalisatie
De reflectie-prompt wordt slim gecomprimeerd:
| Moment | Wat wordt getoond | ~Tokens |
|--------|-------------------|---------|
| Step 1 (eerste keer) | Volledige vragen met keys en scores | ~600 |
| Step 2+ (daarna) | 🪞 Analytische Denker \| tool_name \| Step N | ~15 |
| Hook (2e+ prompt) | Required keys lijst (via daemon query) | ~30 |
De identity context toont nu expliciete key names bij elke vraag:
Required keys for _reflection.answers: efficiency, reusable, result, blockers, ...
1. [key="efficiency"] Was this the fastest route?
2. [key="reusable"] Can I reuse this?Dit voorkomt dat de AI de verkeerde keys raadt (bijv. "reusability" i.p.v. "reusable").
Tools
How-To Knowledge
| Tool | Description |
|------|-------------|
| stella_howto_search | Semantic search in knowledge base |
| stella_howto_get | Get specific how-to by slug |
| stella_howto_upsert | Create new how-to or full rewrite. Duplicate check blocks creation if similar howtos exist (bypass with force=true) |
| stella_howto_patch | Incremental edits: old_text→new_text patches + optional metadata. Saves tokens on large how-tos |
| stella_howto_rate | Rate een how-to na gebruik |
| stella_howto_stats | Usage en score statistieken |
| stella_howto_publish | Toggle public/private |
| stella_howto_akkoord | Bevestig basis instructies gelezen |
| stella_howto_status | Rapporteer voortgang op actieve sessie |
| stella_howto_delete | Permanent verwijderen van howto + versies + usage history |
Reflection System
| Tool | Description |
|------|-------------|
| stella_reflect | Submit een reflectie (met empathy questions) |
| stella_confirm_identity | Bevestig identiteit bij sessie start |
| stella_set_identity | Wissel actieve identiteit mid-sessie |
Bug Reports (centralized in ralph-manager)
Bug tools are only mapped in ralph-manager MCP. Other MCPs don't expose these tools — instead, the bug action gate redirects to ralph-manager and offers howto_status dismiss_bug=true to unblock.
| Tool | Description |
|------|-------------|
| stella_report_bug | Meld een bug of feature request |
| stella_list_bugs | Lijst bugs met filters |
| stella_get_bug | Bug details ophalen |
| stella_update_bug | Bug status updaten |
Plans & Summaries
| Tool | Description |
|------|-------------|
| stella_plan_create | Maak een plan met stappen, gelinkt aan howto sessie(s) |
| stella_plan_update | Update stap status (in_progress/completed/skipped) |
| stella_plan_get | Haal actief plan op met voortgang |
| stella_summarize | Dien samenvatting in na plan completion (met embedding voor semantic search) |
Project Context
| Tool | Description |
|------|-------------|
| stella_set_project_context | Selecteer project, laadt deploy_config, ontgrendelt write tools |
Batch
| Tool | Description |
|------|-------------|
| stella_batch_tools | Meerdere tools in 1 call (telt als 1 reflectie-stap) |
Gate Flow
Session Start
1. Akkoord gate: BLOKKEERT tot stella_howto_akkoord wordt aangeroepen
Hook levert instructies maar geeft GEEN auto-akkoord — dit forceert
de volledige learning workflow (search → plan → execute → summarize → rate)
2. Identity gate: auto-loaded via daemon (eerste MCP call laadt identity)
Fallback: blokkeert tot stella_confirm_identity
During Work
3. Evaluation gate: na X tool calls, vereist status report
4. Summary gate: blokkeert howto_rate tot summary ingediend (na plan completion)
5. Plan gate: blokkeert non-stella tools tot plan aangemaakt (bij open howto sessie)
6. Reflection gate: periodiek reflectie vereist
7. Project context gate: write tools vereisen actief project
-> Permission gate: free/confirm/approve/block per tool
-> Auto-update: deploy_config bijwerken na succesvolle writes
-> Bug action gate: bij probleem, blokkeert tot stella_report_bug (of dismiss_bug)
-> Howto action gate: bij inzicht, blokkeert tot stella_howto_patch (kleine edits) of stella_howto_upsert (nieuw/rewrite)
After Task
-> Summary vereist (stella_summarize)
-> Review vereist (stella_howto_rate of _review)Plan & Summary Workflow
howto_search → howto_get → plan_create → [tools + plan_update] → summarize → howto_rate- Plan gate: zodra een howto sessie open is, worden non-stella tools geblokkeerd tot een plan is aangemaakt
- Summary gate: zodra een plan completed is, wordt howto_rate geblokkeerd tot een summary is ingediend
- Disable:
STELLA_PLAN_GATE=falsein environment, ofconfigurePlanGate(false)in code
Howto Visibility: Scope Isolation + Public/Private
Alle projecten delen dezelfde Supabase. Elke howto heeft twee velden die bepalen wie het kan zien:
scope_id — "Van welk project is dit?"
Komt van LEARNING_SCOPE_ID env var. Bij upsert krijgt een howto de scope_id van de aanmakende server.
is_public — "Mogen andere scopes dit zien?"
Nieuwe howto's starten als is_public: true (public by default). Alleen private maken als het echt project-specifiek is. Quality gates (score >= 4, usage >= 3) gelden voor publishen van private → public.
Scope Filter Logica
Twee lookup strategieën:
// 1. applyScopeFilter (search, stats, basis laden):
// → is_public = true OR scope_id IS NULL OR scope_id = myScopeId
applyScopeFilter(query, includePublic=true)
// 2. findHowtoForEdit (get, upsert, patch, rate, publish, delete):
// → Fetcht ALLE matches: scope_id = myScopeId OR scope_id IS NULL
// → Disambigueert: prefereert eigen scope > public (null) > eerste match
// → Geen maybeSingle() crash bij duplicates
const { data, error } = await findHowtoForEdit(slug);Wat elke server ziet
| Operatie | Private howto (is_public=false) | Public howto (is_public=true) |
|----------|--------------------------------|-------------------------------|
| howto_search | Alleen eigen scope + scope=NULL | Alle scopes |
| howto_get | Eigen scope + scope=NULL | Eigen scope + scope=NULL |
| howto_rate | Eigen scope + scope=NULL | Eigen scope + scope=NULL |
| howto_upsert | Eigen scope + scope=NULL (prefers own) | Eigen scope + scope=NULL (prefers own) |
| howto_patch | Eigen scope + scope=NULL (prefers own) | Eigen scope + scope=NULL (prefers own) |
| howto_publish | Eigen scope + scope=NULL | Eigen scope + scope=NULL |
| howto_delete | Eigen scope + scope=NULL | Eigen scope + scope=NULL |
| Basis laden | Alleen eigen scope | Alleen eigen scope |
Voorbeeld
snelstart-mcp maakt howto "btw-controle" → scope_id="snelstart-mcp", is_public=false
→ adgency kan het NIET zien
howto_publish slug="btw-controle" is_public=true
→ adgency kan het WEL zien via search/get/rate
stella_howto_stats toont: "btw-controle (v3) — ★4.5 (7x) 🌐" (🌐 = public, 🔒 = private)Semantic Search (stella_match_howtos RPC)
De Supabase RPC functie filtert op filter_scope_id:
- Retourneert howto's met
scope_id = myScopeIdOFscope_id IS NULLOFis_public = true - Resultaten zijn gerankt op cosine similarity (pgvector embeddings)
Actieve MCP Servers
| Server | Scope ID | Bug tools | Bijzonderheden |
|--------|----------|-----------|----------------|
| ralph-manager | ralph-manager | Ja (centraal) | ralph_control op approve, permission tools op confirm |
| agentrook | agentrook | Nee | Brand management, logo generatie, ad campaigns |
| web-mcp | web-mcp | Nee | 15 write tools op approve (DNS, domains, Railway, Netlify) + project context gate |
| snelstart-mcp | <administratie-id> | Nee | Boekjaar validatie als pre-gate hook |
| sparkbuddy-live | sparkbuddy | Nee | Eigen AccessLevel auth (user/admin/superadmin) naast Stella permissions |
| vincifox | vincifox | Nee | Howto + reflectie tools |
| stella-proxy | stella-proxy | Nee | Howto + reflectie tools + DataForSEO + Supabase |
| herald-mcp | herald | Nee | Howto + reflectie tools + CRM/email |
Project Structure
stella/
hooks/
stella-gate.sh → UserPromptSubmit hook (symlinked from ~/.claude/hooks/)
Levert instructies (1e prompt only), start daemon, registreert CLI PID, injecteert reflection keys (1x)
stella-howto-hooks.sh → Pre/PostToolUse + Stop hook (symlinked from ~/.claude/hooks/)
Query daemon voor actieve howto hooks, voert ze uit, logt naar ~/.claude/logs/stella/
src/
index.ts → Exports, stellaTools, getStellaHandler
gates.ts → withGates(), checkGates(), gate orchestration (7+ gates)
daemon.ts → Daemon server process (JSON-RPC over Unix socket)
daemon-client.ts → Daemon client (connect, local cache, broadcast sync)
daemon-spawn.ts → Auto-start daemon (fork, PID check, stale cleanup)
plans.ts → plan_create, plan_update, plan_get, summarize
howtos.ts → howto tools + session management + evaluation gate + hook registration
claude-hooks.ts → Hook normalisation, Claude Code config generation, summarize
chat-sessions.ts → Chat session tracking
reflections.ts → reflectie systeem + identity gates + reflection key push
permissions.ts → configurePermissions(), checkPermission(), resolvePermission()
project-context.ts → Project context gate, deploy_config auto-update
bugreports.ts → Bug report tools (centralized in ralph-manager)
projects.ts → Project management tools
tasks.ts → Task management tools
batch.ts → stella_batch_tools (meerdere tools in 1 call)
session.ts → MCP session ID management
embedding.ts → OpenAI embedding generation (text-embedding-3-small)
token-tracker.ts → Token usage tracking per session
token-stats.ts → stella_token_stats, stella_token_top_sessions/tools
howto-analytics.ts → stella_howto_analytics (usage stats)
date-params.ts → Date range parameter parsing
tool-names.ts → Tool name resolution (stella_* ↔ host names)
supabase-client.ts → Supabase connection, scope filtering, requireLearning
types.ts → TypeScript types, helpers, constants
constants.ts → Tool names, default config values
src/neural/
index.ts → Re-exports for all neural modules
window-extractor.ts → EpisodeWindow types, channel key extraction, top-K, novelty/mismatch
coactivation.ts → Hebbian co-activation processor (pairs that fire together → edge strengthening)
neighborhood.ts → BFS graph traversal, Jaccard similarity, decision tracing
bundle-detector.ts → Clique detection + promotion to bundle nodes
policy-engine.ts → PolicyEngine class, heuristic + Thompson Sampling, StateOut
predictors.ts → Tool health predictors: time-windowed trends (24h/7d/30d), outcome-based success rates, trend detection, PredictorStats/ToolTrend exports
src/store/
daemon-store-client.ts → DaemonStoreClient — socket proxy to daemon + graph proxy
tests/
daemon-e2e.mjs → 50 tests: lifecycle, state, multi-client, plans, howtos
daemon-client-e2e.mjs → 24 tests: auto-spawn, fallback, API functions
dist/ → Compiled JavaScript (npm package output)
package.json → @soulbatical/stellaBuild & Test
# Build
npm run build
# Run daemon tests (74 tests)
node tests/daemon-e2e.mjs
node tests/daemon-client-e2e.mjs
# After stella changes: rebuild consumers
cd ~/projecten/web-mcp && npm run build
cd ~/projecten/snelstart-mcp/mcp && npm run build
cd ~/projecten/ralph-manager/backend-mcp && npm run build
cd ~/projecten/adgency/backend-mcp && npm run buildLicense
MIT
