## Asset Header - **Asset ID:** ARQ-XX-AI-SherpaOS-DataModel-EventLedger-v0.1 - **Version:** v00 - **Status:** Draft - **Owner:** Victor Heredia - **IntellBank:** IB-XX-Maestro - **Tipo:** ARQ — Architecture - **Propósito:** AI Sherpa OS — Data Model & Event Ledger - **Última actualización:** 2026-04-11 --- # AI Sherpa OS — Data Model & Event Ledger ## Engineering Spec v0.1 **EmpowerLabs / MasterPlaybooks** Última actualización: 24 marzo 2026 --- ## Contexto Este documento traduce el modelo de datos y el Event Ledger en especificaciones accionables para el equipo de backend. **Actualización v0.1:** Se incorpora el stack técnico validado basado en el patrón OB1 (Supabase + pgvector + OpenRouter + MCP). Esta implementación sirve simultáneamente al AI Sherpa OS y a Mi Sherpa IA as a Service — misma infraestructura, dos productos. **Responsable de implementación:** Alex + equipo técnico **Referencia de implementación:** Patrón OB1 (NateBJones-Projects/OB1) — validado como implementable en días por un solo desarrollador. --- ## Índice 1. Stack técnico concreto 2. Setup inicial — Supabase 3. Schema de base de datos 4. Edge Functions 5. Catálogo de event_type 6. Ejemplos de payload 7. API Endpoints 8. Herramientas MCP 9. Flujo end-to-end 10. Reglas de estado 11. Definition of Done 12. Orden de implementación recomendado --- ## 1. Stack técnico concreto | Componente | Tecnología | Por qué | |------------|------------|---------| | Base de datos | **Supabase** (PostgreSQL) | Todo en uno: DB + auth + storage + realtime + edge functions | | Vector search | **pgvector** (extensión Postgres) | Búsqueda semántica sin Pinecone/Weaviate — suficiente para esta fase | | Serverless logic | **Supabase Edge Functions** (Deno) | Metadata extraction + embedding generation al guardar eventos | | LLM gateway | **OpenRouter** | Multi-modelo (Claude, GPT, Gemini) desde una sola API key | | Protocolo tools | **MCP** (Model Context Protocol) | Interfaz estándar para que el Sherpa use las herramientas de memoria | | Autenticación | **Supabase Auth** | JWT + Row Level Security por user_id | | Aislamiento | **Row Level Security (RLS)** | Cada usuario ve solo sus datos — crítico para multi-tenant | **Nota:** Este es el mismo stack de Mi Sherpa IA. Si Alex ya tiene un proyecto Supabase corriendo para Mi Sherpa IA, el Sherpa puede usar la misma instancia con tablas adicionales y RLS separado por contexto. --- ## 2. Setup inicial — Supabase ### 2.1 Habilitar extensión pgvector ```sql -- Ejecutar en SQL Editor de Supabase create extension if not exists vector; ``` ### 2.2 Configurar OpenRouter ```bash # En variables de entorno de Supabase (Settings → Edge Functions → Secrets) OPENROUTER_API_KEY=sk-or-... OPENROUTER_MODEL=anthropic/claude-3-haiku # modelo rápido para embeddings/metadata ``` ### 2.3 Modelo para embeddings ```bash # OpenAI embeddings via OpenRouter (1536 dimensiones — compatible con text-embedding-3-small) EMBEDDING_MODEL=openai/text-embedding-3-small ``` --- ## 3. Schema de base de datos ### 3.1 Tabla: users ```sql create table users ( user_id uuid primary key default gen_random_uuid(), email text unique not null, name text, profile jsonb default '{}'::jsonb, -- profile incluye: goal, industry, role, experience_level created_at timestamptz default now(), updated_at timestamptz default now() ); -- RLS alter table users enable row level security; create policy "users_own_data" on users for all using (auth.uid() = user_id); -- Auto-update timestamp create trigger update_users_updated_at before update on users for each row execute function update_updated_at_column(); ``` ### 3.2 Tabla: sessions ```sql create table sessions ( session_id uuid primary key default gen_random_uuid(), user_id uuid references users(user_id) not null, sherpa_id text not null, -- ej: 'sherpa-nom035', 'sherpa-recruitment' playbook_id text, -- MasterPlaybook en ejecución started_at timestamptz default now(), ended_at timestamptz, summary text, -- generado automáticamente al cerrar metadata jsonb default '{}'::jsonb ); -- RLS alter table sessions enable row level security; create policy "sessions_own_data" on sessions for all using (auth.uid() = user_id); ``` ### 3.3 Tabla: events ← EL CORAZÓN DEL SISTEMA ```sql create table events ( event_id uuid primary key default gen_random_uuid(), user_id uuid references users(user_id) not null, session_id uuid references sessions(session_id), -- Clasificación del evento event_type text not null, -- ver catálogo completo abajo event_category text not null, -- interaction | progress | artifact | signal | commercial -- Datos del evento payload jsonb default '{}'::jsonb, -- Embedding semántico generado automáticamente embedding vector(1536), -- Trazabilidad created_at timestamptz default now(), -- Contexto del playbook (opcional) playbook_id text, module_id text ); -- RLS alter table events enable row level security; create policy "events_own_data" on events for all using (auth.uid() = user_id); -- Índice para búsqueda semántica create index events_embedding_idx on events using ivfflat (embedding vector_cosine_ops) with (lists = 100); -- Índice para queries por usuario + tiempo create index events_user_created_idx on events (user_id, created_at desc); ``` ### 3.4 Tabla: state (derivado del Event Ledger) ```sql create table state ( user_id uuid primary key references users(user_id), -- Estado del usuario active_goal text, current_stage text, progress_score integer default 0, -- 0-100 -- Niveles derivados engagement_level text default 'medium', -- high | medium | low readiness_level text default 'low', -- high | medium | low risk_level text default 'low', -- high | medium | low (riesgo de abandono) -- Siguiente acción recomendada next_best_action text, next_best_action_payload jsonb default '{}'::jsonb, -- Metadata last_event_id uuid, updated_at timestamptz default now() ); -- RLS alter table state enable row level security; create policy "state_own_data" on state for all using (auth.uid() = user_id); ``` ### 3.5 Tabla: memories ← MEMORY LAYER (patrón OB1) ```sql create table memories ( memory_id uuid primary key default gen_random_uuid(), user_id uuid references users(user_id) not null, -- Contenido — debe ser autocontenido y tener sentido fuera de contexto content text not null, -- Embedding para búsqueda semántica embedding vector(1536), -- Metadata extraída automáticamente por LLM metadata jsonb default '{}'::jsonb, -- metadata incluye: people[], topics[], action_items[], type, source_session_id -- Tipo de memoria memory_type text default 'episodic', -- episodic | identity | inference | summary -- Trazabilidad source_session_id uuid references sessions(session_id), created_at timestamptz default now(), updated_at timestamptz default now() ); -- RLS alter table memories enable row level security; create policy "memories_own_data" on memories for all using (auth.uid() = user_id); -- Índice para búsqueda semántica create index memories_embedding_idx on memories using ivfflat (embedding vector_cosine_ops) with (lists = 100); -- Auto-update timestamp create trigger update_memories_updated_at before update on memories for each row execute function update_updated_at_column(); ``` ### 3.6 Tabla: artifacts ```sql create table artifacts ( artifact_id uuid primary key default gen_random_uuid(), user_id uuid references users(user_id) not null, session_id uuid references sessions(session_id), artifact_type text not null, -- plan | summary | checklist | framework | report title text, content jsonb not null, created_at timestamptz default now() ); -- RLS alter table artifacts enable row level security; create policy "artifacts_own_data" on artifacts for all using (auth.uid() = user_id); ``` ### 3.7 Función de utilidad: update_updated_at_column ```sql create or replace function update_updated_at_column() returns trigger as $$ begin new.updated_at = now(); return new; end; $$ language plpgsql; ``` --- ## 4. Edge Functions ### 4.1 on-event-created (trigger automático) Se ejecuta cada vez que se inserta un evento. Genera embedding y extrae metadata. ```typescript // supabase/functions/on-event-created/index.ts import { serve } from "https://deno.land/std@0.168.0/http/server.ts" import { createClient } from "https://esm.sh/@supabase/supabase-js@2" serve(async (req) => { const { event_id, user_id, event_type, payload } = await req.json() const supabase = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! ) // 1. Generar texto para el embedding const textToEmbed = `${event_type}: ${JSON.stringify(payload)}` // 2. Generar embedding via OpenRouter const embeddingResponse = await fetch('https://openrouter.ai/api/v1/embeddings', { method: 'POST', headers: { 'Authorization': `Bearer ${Deno.env.get('OPENROUTER_API_KEY')}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'openai/text-embedding-3-small', input: textToEmbed }) }) const { data } = await embeddingResponse.json() const embedding = data[0].embedding // 3. Actualizar el evento con el embedding await supabase .from('events') .update({ embedding }) .eq('event_id', event_id) // 4. Recalcular state await recalculateState(supabase, user_id) return new Response(JSON.stringify({ success: true })) }) ``` ### 4.2 on-session-end (genera memory al cerrar sesión) ```typescript // supabase/functions/on-session-end/index.ts serve(async (req) => { const { session_id, user_id } = await req.json() // 1. Obtener todos los eventos de la sesión const { data: events } = await supabase .from('events') .select('*') .eq('session_id', session_id) .order('created_at', { ascending: true }) // 2. Generar resumen de sesión via LLM (OpenRouter) const summary = await generateSessionSummary(events) // 3. Guardar resumen en sessions await supabase .from('sessions') .update({ summary, ended_at: new Date().toISOString() }) .eq('session_id', session_id) // 4. Crear memoria episódica const memoryContent = buildMemoryContent(summary, events) const metadata = await extractMetadata(memoryContent) const embedding = await generateEmbedding(memoryContent) await supabase.from('memories').insert({ user_id, content: memoryContent, embedding, metadata, memory_type: 'episodic', source_session_id: session_id }) return new Response(JSON.stringify({ success: true, summary })) }) ``` ### 4.3 search-memories (búsqueda semántica) ```typescript // supabase/functions/search-memories/index.ts serve(async (req) => { const { query, user_id, limit = 5 } = await req.json() // 1. Generar embedding del query const queryEmbedding = await generateEmbedding(query) // 2. Búsqueda por similitud coseno const { data: memories } = await supabase.rpc('search_memories', { query_embedding: queryEmbedding, user_id_param: user_id, match_count: limit }) return new Response(JSON.stringify({ memories })) }) ``` ```sql -- Función SQL para búsqueda semántica create or replace function search_memories( query_embedding vector(1536), user_id_param uuid, match_count int default 5 ) returns table ( memory_id uuid, content text, metadata jsonb, similarity float ) language sql stable as $$ select memory_id, content, metadata, 1 - (embedding <=> query_embedding) as similarity from memories where user_id = user_id_param order by embedding <=> query_embedding limit match_count; $$; ``` --- ## 5. Catálogo de event_type ### Interaction | event_type | Descripción | |------------|-------------| | `user_message_submitted` | Usuario envía un mensaje al Sherpa | | `sherpa_response_generated` | Sherpa genera una respuesta | | `user_goal_declared` | Usuario declara explícitamente un objetivo | | `user_constraint_declared` | Usuario declara una limitante o restricto | | `session_started` | Inicio de sesión | | `session_ended` | Cierre de sesión | ### Progress | event_type | Descripción | |------------|-------------| | `module_started` | Usuario inicia un módulo del MasterPlaybook | | `module_completed` | Usuario completa un módulo | | `task_completed` | Usuario completa una tarea específica | | `milestone_reached` | Usuario alcanza un hito del playbook | | `content_consumed` | Usuario consumió un contenido (video, artículo) | ### Artifact | event_type | Descripción | |------------|-------------| | `plan_generated` | Sherpa generó un plan de acción | | `summary_generated` | Sherpa generó un resumen | | `checklist_generated` | Sherpa generó un checklist | | `framework_applied` | Sherpa aplicó un framework al contexto del usuario | ### Signal (derivado — generado por el State Engine) | event_type | Descripción | |------------|-------------| | `engagement_increase_detected` | Señal positiva de engagement | | `engagement_drop_detected` | Señal de caída de engagement (riesgo abandono) | | `blocker_detected` | Usuario expresó un bloqueo | | `readiness_increase_detected` | Usuario está más listo para siguiente paso | ### Commercial (especialización) | event_type | Descripción | |------------|-------------| | `pricing_interest_detected` | Usuario preguntó por precio | | `urgency_detected` | Usuario expresó urgencia | | `call_intent_detected` | Usuario quiere hablar con alguien | | `upgrade_interest_detected` | Usuario preguntó por tier superior | --- ## 6. Ejemplos de payload por evento ### user_goal_declared ```json { "goal_text": "Quiero implementar NOM-035 en mi empresa de 80 personas antes de junio", "goal_type": "compliance", "timeframe": "90_days", "priority": "high", "company_size": "80_employees" } ``` ### module_completed ```json { "module_id": "NOM035-001", "module_name": "Diagnóstico inicial NOM-035", "completion_time_seconds": 1240, "score": 85, "playbook_id": "sherpa-nom035-v1" } ``` ### blocker_detected ```json { "blocker_text": "No tenemos presupuesto asignado para esto todavía", "blocker_type": "budget", "severity": "medium", "raw_message": "El problema es que no tenemos..." } ``` ### pricing_interest_detected ```json { "phrase": "¿Cuánto costaría implementar el programa completo?", "interest_level": "high", "context": "Al terminar el módulo de diagnóstico", "related_offer": "Programa NOM-035 Premium" } ``` --- ## 7. API Endpoints ### POST /events Registra un evento en el ledger. El embedding se genera automáticamente via edge function. ``` Body: { "user_id": "uuid", "session_id": "uuid", "event_type": "module_completed", "event_category": "progress", "payload": { ... }, "playbook_id": "sherpa-nom035-v1", // opcional "module_id": "NOM035-001" // opcional } Response: { "event_id": "uuid", "status": "created", "state_updated": true } ``` ### GET /state/{user_id} Obtiene el estado consolidado del usuario. ``` Response: { "user_id": "uuid", "active_goal": "Implementar NOM-035 antes de junio", "current_stage": "diagnostic", "progress_score": 35, "engagement_level": "high", "readiness_level": "medium", "risk_level": "low", "next_best_action": "complete_module_2", "next_best_action_payload": { "module_id": "NOM035-002", "message": "Estás listo para el siguiente módulo: Política de Prevención" }, "updated_at": "2026-03-24T..." } ``` ### GET /recommendations/{user_id} Obtiene recomendaciones activas para el usuario. ``` Response: { "recommendations": [ { "type": "next_module", "priority": "high", "title": "Continuar con Módulo 2", "action": "navigate_to_module", "payload": { "module_id": "NOM035-002" } }, { "type": "resource", "priority": "medium", "title": "Guía de diagnóstico rápido", "action": "show_resource" } ] } ``` ### POST /session/start ``` Body: { "user_id": "uuid", "sherpa_id": "sherpa-nom035", "playbook_id": "..." } Response: { "session_id": "uuid", "started_at": "..." } ``` ### POST /session/end Cierra sesión, genera resumen y crea memoria episódica automáticamente. ``` Body: { "session_id": "uuid", "user_id": "uuid" } Response: { "summary": "...", "memory_id": "uuid", "duration_seconds": 840 } ``` ### POST /memories/search Búsqueda semántica en la Memory Layer. ``` Body: { "user_id": "uuid", "query": "¿Qué objetivos declaró el usuario?", "limit": 5 } Response: { "memories": [{ "content": "...", "similarity": 0.87, "metadata": {...} }] } ``` --- ## 8. Herramientas MCP El Sherpa accede a la infraestructura via estas 6 herramientas MCP: | Herramienta | Descripción | |-------------|-------------| | `capture_event` | Registrar un evento en el Event Ledger | | `get_user_state` | Obtener estado actual del usuario | | `get_recommendations` | Obtener recomendaciones activas | | `capture_memory` | Guardar una memoria episódica manualmente | | `search_memories` | Búsqueda semántica en el Memory Layer | | `list_recent_memories` | Listar memorias recientes del usuario | --- ## 9. Flujo end-to-end ``` 1. Sherpa recibe mensaje del usuario └→ capture_event: user_message_submitted 2. Sherpa consulta contexto └→ search_memories: "¿qué sé de este usuario?" └→ get_user_state: estado actual 3. Sherpa genera respuesta contextualizada └→ capture_event: sherpa_response_generated 4. Usuario completa módulo └→ capture_event: module_completed └→ State Engine recalcula automáticamente 5. State Engine detecta progreso └→ capture_event: engagement_increase_detected (derivado) └→ next_best_action actualizado 6. Usuario menciona presupuesto └→ capture_event: pricing_interest_detected └→ Integration Layer notifica al CRM 7. Sesión termina └→ POST /session/end └→ Resumen generado automáticamente └→ Memoria episódica creada └→ Próxima sesión inicia con contexto completo ``` --- ## 10. Reglas de recálculo de estado ``` State se recalcula cuando: - Se registran N eventos nuevos (N=5 por defecto) - Se cierra una sesión - Se detecta una señal comercial Lógica de engagement_level: - HIGH: módulo_completado en últimos 7 días Y tiempo_sesión > 10min - LOW: sin eventos en últimos 14 días O blocker_detected sin resolución - MEDIUM: resto de casos Lógica de readiness_level: - HIGH: progress_score > 60 AND engagement_level = HIGH - LOW: progress_score < 20 OR blocker_detected reciente - MEDIUM: resto de casos Lógica de risk_level: - HIGH: engagement_drop_detected en últimas 2 sesiones - MEDIUM: sin actividad en 7-14 días - LOW: actividad regular Prioridad de eventos: recientes > históricos (decay function) ``` --- ## 11. Definition of Done ### Fase 1 — Event Ledger v0.1 - [ ] Proyecto Supabase creado con extensión pgvector habilitada - [ ] Schema completo ejecutado sin errores (users, sessions, events, state) - [ ] RLS configurado y verificado por user_id - [ ] Edge function `on-event-created` deployada y funcional - [ ] POST /events funcional — evento persistido + embedding generado - [ ] GET /state/{user_id} funcional — estado calculado automáticamente - [ ] Flujo end-to-end ejecutable: session_start → event → state_update - [ ] Al menos 5 event_types del catálogo implementados y probados ### Fase 2 — Memory Layer v0.1 - [ ] Tabla `memories` creada con embedding vector(1536) - [ ] Edge function `on-session-end` deployada y funcional - [ ] Función SQL `search_memories` funcionando con cosine similarity - [ ] POST /session/end genera resumen + memoria episódica automáticamente - [ ] POST /memories/search retorna resultados relevantes - [ ] 6 herramientas MCP operativas y documentadas --- ## 12. Orden de implementación recomendado ``` Día 1: ├── Crear proyecto Supabase ├── Habilitar pgvector ├── Ejecutar schema: users + sessions + events + state └── Verificar RLS Día 2: ├── Edge function: on-event-created (embedding generation) ├── POST /events funcional └── GET /state/{user_id} básico Día 3: ├── Schema: memories ├── Edge function: on-session-end (summary + memory creation) ├── POST /session/start y POST /session/end └── Prueba flujo completo end-to-end Día 4: ├── Función SQL: search_memories ├── POST /memories/search ├── Herramientas MCP (6 herramientas) └── Prueba de búsqueda semántica Día 5: ├── Integration Layer: webhook hacia CRM para señales comerciales ├── Documentación de API para Juan Carlos └── Demo interno del flujo completo ``` --- ## Cierre técnico Este spec permite a Alex y su equipo implementar el núcleo completo del Sherpa OS y de Mi Sherpa IA as a Service en una sola semana, usando una sola plataforma (Supabase), sin dependencias externas complejas. Sin esto, el sistema queda en prompts. Con esto, se convierte en infraestructura. La misma infraestructura que sirve al Sherpa hoy, sirve a Mi Sherpa IA as a Service mañana — y eso es exactamente lo que hace que esta inversión valga doble. --- *Documento: ai_sherpa_os_data_model_event_ledger_v0.1.md | EmpowerLabs | 24 marzo 2026*