Twin

Twin

Tu yo digital portable: una IA que te conoce y se conecta a otras apps, como un "Login con Google" pero para tu personalidad.
πŸ›Έ Future
NicolΓ‘s Priotto
NicolΓ‘s Priotto
Manuel Ahumada
Manuel Ahumada
Juan Ignacio Lategana
Juan Ignacio Lategana
Josefina Gonzalez Cornet
Josefina Gonzalez Cornet

Twin Protocol

Twin

Track: πŸ›Έ Future Β· Equipo 11 Β· Platanus Hack 26 (Buenos Aires)


TL;DR

"Login con Google" pero para tu personalidad.

Twin es un protocolo de identidad personal portable: cada persona crea una representaciΓ³n AI de sΓ­ misma β€”gustos, hΓ‘bitos, vibras, estilo de comunicaciΓ³nβ€” mediante entrevistas de voz cortas. Las aplicaciones de terceros se integran con un botΓ³n Connect your Twin (flujo tipo OAuth), piden scopes especΓ­ficos, y a partir de ahΓ­ pueden consultar al Twin vΓ­a API REST para personalizar su experiencia desde el primer click.

El usuario controla en todo momento quΓ© apps pueden consultar quΓ© dominios, quΓ© consultas hicieron y puede revocar acceso a cualquiera. La idea no es construir otro chatbot β€” es construir la infraestructura para que el contexto del usuario deje de ser un silo cerrado en cada plataforma.

AsΓ­ como hoy uno hace "Login con Google", la apuesta es que maΓ±ana sea "Login con Twin": al conectarte, tu Twin expone una API que las plataformas usan para adaptar su producto, contenido e interfaz al usuario que reciΓ©n se registra.

Demo end-to-end con una app mock construida para esta demo: Buholingo, una plataforma de aprendizaje de idiomas (estilo Duolingo) que conecta el Twin para personalizar las lecciones segΓΊn los gustos y vibras del usuario.

Tesis larga, decisiones arquitectΓ³nicas, scopes, dominios y modelo de datos completo en IDEA.md. CatΓ‘logo de constantes (scopes, dominios, intents, curriculum, providers) en DEFINITIONS.md.


Tabla de contenidos

  1. Problema y tesis
  2. CΓ³mo funciona
  3. Arquitectura
  4. Stack tΓ©cnico
  5. Modelo de datos
  6. Twin Query API
  7. Connect Flow (OAuth-like)
  8. Permission engine
  9. Sesiones de entrenamiento (voice agents)
  10. Estado de implementaciΓ³n
  11. CΓ³mo correrlo localmente
  12. Demo paths
  13. App de demo: Buholingo
  14. Por quΓ© cada criterio

Problema y tesis

Hoy cada plataforma tiene que aprender quiΓ©n sos desde cero: configurΓ‘s preferencias, llenΓ‘s formularios, entrenΓ‘s algoritmos a click hasta que la app entiende tus gustos. Spotify conoce tu mΓΊsica, Mercado Libre tus compras, Instagram tus intereses, ChatGPT parte de tu contexto, pero no existe una representaciΓ³n portable y controlada de vos que las apps puedan consultar.

Las experiencias del futuro van a estar mediadas por agentes y van a ser profundamente personalizadas. Para eso, las apps necesitan entender al usuario. Hay mucha inversiΓ³n hoy en MCPs y APIs para que agentes puedan usar software, pero falta el otro lado: que los agentes y aplicaciones realmente entiendan a la persona detrΓ‘s del usuario.

AsΓ­ como OAuth permite que una app acceda a tu identidad sin pedirte tu contraseΓ±a, Twin permite que una app acceda a tu contexto personal sin tener que poseer tus datos ni inferirlos desde cero.


CΓ³mo funciona

1. El usuario crea su Twin

El usuario se registra y entrena su Twin mediante un curriculum de 8 sesiones de voz de ~15 minutos (~2h totales). El diseΓ±o estΓ‘ inspirado en el paper de Stanford / Google DeepMind "Generative Agent Simulations of 1,000 People", que demostrΓ³ que con 2 horas de entrevista alcanzan para que un agente prediga las respuestas del entrevistado con 85% de precisiΓ³n sobre tests posteriores (GSS, BFI, juegos econΓ³micos).

Cada sesiΓ³n:

  • Tiene un target domain (vibes, music_taste, event_preferences, etc.) asignado por el slot del curriculum.
  • Las preguntas las genera el LLM en runtime (no hay banco fijo) en base al estado actual del Twin + el slot.
  • Al cerrar: extracciΓ³n de facts β†’ update de twin_skills (con confidence per-fact) β†’ recΓ‘lculo de completion_score β†’ avance de next_session_index β†’ regeneraciΓ³n del summary.

El Twin es una entidad estructurada en Postgres, no un modelo fine-tuned. Esto evita infraestructura ML pesada y mantiene el conocimiento auditable y editable.

2. Aplicaciones externas se conectan

Una app de terceros (ej. Buholingo β€” la app mock de aprendizaje de idiomas que armamos para la demo) integra un botΓ³n:

<a href="https://twin.app/connect?app_id=buholingo&redirect_uri=...">
  Connect your Twin
</a>

El usuario es redirigido a un hosted consent screen controlado por Twin Protocol. Acepta los scopes, se crea una app_connection, y la app recibe un connection_id + access_token (SHA-256 hasheado en DB; el raw nunca se persiste).

3. La app consulta la Twin API

POST /api/twin/query
Authorization: Bearer <access_token>
{
  "connection_id": "conn_abc123",
  "intent": "event_ranking",
  "context": { "events": [ ... ] }
}

Twin valida scopes, ejecuta el intent contra el contexto del Twin (vΓ­a Claude Sonnet 4.6), y responde con respuesta estructurada + confidence + reasons + el objeto policy que dice quΓ© scopes se usaron.

4. El usuario audita y controla

En Aplicaciones, el usuario ve cada app conectada, quΓ© scopes tiene, quΓ© consultas hizo recientemente, y puede revocar la conexiΓ³n con un click. Las consultas bloqueadas (por scope insuficiente o dominio sensible) tambiΓ©n quedan logueadas para transparencia.

Como el Twin se entrena con sesiones cortas y frecuentes, se mantiene siempre actualizado sin reentrenarlo de cero β€” cada nueva conversaciΓ³n afina los facts existentes y suma los nuevos.


Arquitectura

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ Twin Protocol (Next.js fullstack) ──────────────────────────┐
β”‚                                                                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Landing     β”‚  β”‚ Auth (Supabase) β”‚  β”‚  Dashboard     β”‚  β”‚  Connect (hosted)   β”‚   β”‚
β”‚  β”‚  /(public)   β”‚  β”‚  /auth/*        β”‚  β”‚  /(platform)   β”‚  β”‚  /(connect)         β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                                                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ /api ────────────────────────────────────────────────┐  β”‚
β”‚  β”‚  /api/twin/query    β†’  4 intents (event_recommendation, event_ranking,          β”‚  β”‚
β”‚  β”‚                         domain_summary, general_summary)                        β”‚  β”‚
β”‚  β”‚  /api/connect/*     β†’  consent β†’ token issuance β†’ revoke                        β”‚  β”‚
β”‚  β”‚  /api/sessions/*    β†’  poll de sesiΓ³n post-end (facts pipeline)                 β”‚  β”‚
β”‚  β”‚  /api/livekit/*     β†’  token mint para sesiones (server-issued)                 β”‚  β”‚
β”‚  β”‚  /api/training/*    β†’  start session                                            β”‚  β”‚
β”‚  β”‚  /api/chat/*        β†’  conversaciΓ³n libre con el Twin (post-MVP)                β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                                                       β”‚
β”‚  Permission engine  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ lib/query ─────────────┐                            β”‚
β”‚  lib/permissions    β”‚  intents/event-ranking.ts          β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚
β”‚  lib/connect        β”‚  intents/event-recommendation.ts   │──▢│ Anthropic API  β”‚       β”‚
β”‚                     β”‚  intents/domain-summary.ts         β”‚   β”‚ Claude 4.6     β”‚       β”‚
β”‚                     β”‚  intents/general-summary.ts        β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚
β”‚                     β”‚  twin-context.ts (DB β†’ prompt)     β”‚                            β”‚
β”‚                     β”‚  log.ts (query_logs)               β”‚                            β”‚
β”‚                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                            β”‚
β”‚                                                                                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚                                                               β”‚
             β–Ό                                                               β–Ό
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚   Supabase          β”‚                                       β”‚  LiveKit Worker        β”‚
   β”‚   - Postgres        β”‚                                       β”‚  (Node, proceso aparte)β”‚
   β”‚   - Auth            β”‚                                       β”‚  - Deepgram Nova-3 STT β”‚
   β”‚   - RLS policies    β”‚                                       β”‚  - Claude 4.6          β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                       β”‚  - ElevenLabs Flash    β”‚
                                                                 β”‚  - Beyond Presence     β”‚
                                                                 β”‚    (avatar Nelly)      β”‚
                                                                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

DecisiΓ³n clave: todo Next.js fullstack en un solo proyecto (front + API). La ΓΊnica excepciΓ³n es el worker LiveKit Agents que corre como proceso Node separado (necesita websockets long-running, no entra en Vercel functions). En dev son 2 procesos (pnpm dev + pnpm worker); en prod, Twin va a Vercel y el worker a Render/Fly.io.


Stack tΓ©cnico

CapaTech
FrameworkNext.js 16 (App Router, TypeScript estricto, Server Components + Server Actions)
Auth + DBSupabase (Postgres con RLS por usuario, Supabase Auth con email + password)
LLMClaude Sonnet 4.6 vΓ­a @anthropic-ai/sdk, con un adapter custom para @livekit/agents (worker/anthropic-llm.ts)
Voice agentsLiveKit Cloud + @livekit/agents (Node worker)
STTDeepgram Nova-3
TTSElevenLabs Flash v2.5 (voz argentina, latencia <300ms)
AvatarBeyond Presence (@livekit/agents-plugin-bey) β€” avatar foto-realista server-rendered, publicado como video track WebRTC. Avatar default: Nelly
UITailwind v4 + shadcn/ui + Lucide icons + DiceBear (avatares 2D del perfil de usuario)
ValidaciΓ³nZod en server actions y request bodies de la API
TestsVitest (~50 unit tests sobre permission engine, recompute de skills, intents, prompts)
HostingVercel (Twin + Buholingo, mismo proyecto) Β· Render/Fly.io (worker LiveKit) Β· Supabase Cloud (DB + Auth)

Modelo de datos

7 tablas en public. Migrations en supabase/migrations/.

users               id, email, name, avatar_url
twins               id, user_id, name, completion_score, summary,
                    profile_json, next_session_index (0..12)
twin_skills         id, twin_id, domain, confidence, facts_json
                    └─ facts_json: [{ id, text, confidence, source_session_id, ... }]
sessions            id, twin_id, type ('training' | 'chat'),
                    domain, transcript_json, summary, extracted_facts_json,
                    session_index, target_domains_json, duration_seconds,
                    started_at, ended_at
twin_skill_edits    id, user_id, twin_id, domain, action ('add' | 'remove' | 'edit'),
                    fact_before, fact_after, reason
developer_apps      id, name, client_id, client_secret_hash,
                    redirect_uris_json, allowed_scopes_json
app_connections     id, user_id, twin_id, app_id, scopes_json, status,
                    access_token_hash (SHA-256), revoked_at
query_logs          id, connection_id, user_id, app_id, intent, question,
                    response_summary, allowed, blocked_reason, scopes_used_json

Highlights del diseΓ±o:

  • twin_skills es la fuente de verdad, no twins.profile_json. Cada fact tiene su propia confidence; la del dominio es la media. Esto permite borrar/corregir un fact sin tocar el resto.
  • Tokens nunca se persisten en raw: solo SHA-256(access_token) queda en DB. La revocaciΓ³n es soft delete (status + revoked_at) para preservar audit trail.
  • communication_style se deriva de los transcripts de las otras sesiones β€” no tiene slot dedicado en el curriculum.
  • RLS habilitado sobre todas las tablas: cada usuario solo accede a sus propias filas vΓ­a auth.uid().

Trigger handle_new_user (en migration 0005) crea automΓ‘ticamente la fila twins con name = 'Twin de <nombre>' cada vez que alguien se registra en auth.users.


Twin Query API

Un ΓΊnico endpoint con mΓΊltiples intent. DiseΓ±ado para que la app externa nunca acceda a datos crudos del usuario β€” solo hace preguntas estructuradas y recibe respuestas con confidence + reasons.

POST /api/twin/query
Authorization: Bearer <access_token>
Content-Type: application/json

Intents soportados (4)

IntentScopes requeridosCaso de uso
general_summarypersona.read.summaryPitch del usuario en 1 pΓ‘rrafo
domain_summarypersona.read.<domain>Dump estructurado de un dominio
event_recommendationpersona.read.music + persona.read.events + persona.ask.recommendation"ΒΏLe gustarΓ­a este evento?"
event_rankingmismo conjuntoReordenar una lista de eventos por afinidad

Ejemplo: event_ranking

Request:

{
  "connection_id": "conn_abc123",
  "intent": "event_ranking",
  "context": {
    "events": [
      { "id": "e1", "artist": "Tormenta Negra", "genres": ["indie", "rock"], "venue_size": "intimate" },
      { "id": "e2", "artist": "Pop Night Live", "genres": ["pop"],          "venue_size": "arena" }
    ]
  }
}

Response:

{
  "ranking": [
    { "id": "e1", "score": 0.87, "match": "strong_match",
      "reasons": ["Indie rock matches user's top genres", "User strongly prefers intimate venues"] },
    { "id": "e2", "score": 0.32, "match": "weak_match",
      "reasons": ["Pop is not a top genre", "User dispreferred large venues"] }
  ],
  "policy": {
    "allowed": true,
    "scopes_used": ["persona.read.music", "persona.read.events", "persona.ask.recommendation"],
    "blocked_reason": null
  }
}

Caso bloqueado:

{
  "policy": {
    "allowed": false,
    "scopes_used": [],
    "blocked_reason": "missing_scope: persona.read.events"
  }
}

Cada intent vive en src/lib/query/intents/*.ts con su propia funciΓ³n pura runIntent(twinContext, request) que llama a Claude con un system prompt domain-specific. El contexto del Twin se arma en lib/query/twin-context.ts desde twin_skills filtrando por scopes autorizados (no se le pasa al LLM informaciΓ³n sobre dominios que la app no puede leer).


Connect Flow (OAuth-like)

ImplementaciΓ³n OAuth-simplificada (sin client_secret/authorization_code/refresh_token por scope MVP). Flujo funcional end-to-end:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Buholingo   β”‚                β”‚  Twin Protocol   β”‚                 β”‚  Supabase   β”‚
β”‚ (3rd party) β”‚                β”‚                  β”‚                 β”‚             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚                                β”‚                                  β”‚
       β”‚ GET /connect?app_id=buholingo  β”‚                                  β”‚
       β”‚ &redirect_uri=...              β”‚                                  β”‚
       │───────────────────────────────▢│                                  β”‚
       β”‚                                β”‚ 1. Validate app_id +             β”‚
       β”‚                                β”‚    redirect_uri (whitelist)      β”‚
       β”‚                                β”‚ 2. Auth check (login if needed)  β”‚
       β”‚                                β”‚ 3. Render consent screen         β”‚
       β”‚                                β”‚    (scopes from app + diff)      β”‚
       β”‚                                β”‚                                  β”‚
       β”‚                                β”‚ POST /api/connect/authorize      β”‚
       β”‚                                │─────────────────────────────────▢│
       β”‚                                β”‚  INSERT app_connections          β”‚
       β”‚                                β”‚  hash = sha256(token)            β”‚
       β”‚                                │◀─────────────────────────────────│
       β”‚                                β”‚                                  β”‚
       β”‚ 302 redirect_uri               β”‚                                  β”‚
       β”‚   ?connection_id=...           β”‚                                  β”‚
       β”‚   &access_token=<raw>          β”‚                                  β”‚
       │◀───────────────────────────────│                                  β”‚
       β”‚                                β”‚                                  β”‚
       β”‚ POST /api/twin/query           β”‚                                  β”‚
       β”‚ Bearer <access_token>          β”‚                                  β”‚
       │───────────────────────────────▢│ Verify token hash + scopes       β”‚
       β”‚                                β”‚ Run intent β†’ log β†’ respond       β”‚
       β”‚                                β”‚                                  β”‚

El consent screen es hosted en Twin Protocol (no en la app de terceros) β†’ consistencia visual + confianza del usuario + menos fricciΓ³n para developers (solo integran el botΓ³n).


Permission engine

Toda request a /api/twin/query pasa por src/lib/permissions/. Pipeline:

  1. Extract token del header Authorization.
  2. Lookup connection por SHA-256(token) con status='active'.
  3. Match intent β†’ scopes segΓΊn tabla en DEFINITIONS.md.
  4. Diff scopes: requeridos vs autorizados β†’ si falta alguno, devolver policy.allowed = false con blocked_reason: "missing_scope: <scope>".
  5. Block sensitive domains por defecto: private_memories, sensitive_topics, politics, health, relationships, financial_status, raw_sources. Bloqueo explΓ­cito por context.domain + clasificaciΓ³n rΓ‘pida del LLM sobre la question (cap secundaria, best-effort).
  6. Run intent sobre twin_context filtrado por scopes.
  7. Log a query_logs sΓ­ o sΓ­ (allowed o blocked).

Tests unit en src/lib/query/__tests__/ cubren cada combinaciΓ³n de intent x scopes faltantes x dominios bloqueados.


Sesiones de entrenamiento (voice agents)

El flujo de voice es la parte mΓ‘s compleja del stack. Vive en worker/ (proceso Node separado) y se conecta vΓ­a LiveKit Cloud.

Browser (Next.js)                    LiveKit Cloud                 Worker (Node)
─────────────────                    ─────────────                 ─────────────

1. Click "Iniciar sesiΓ³n"
   └─ POST /api/training/start
        β”œβ”€ INSERT sessions (status=open)
        β”œβ”€ Mint LiveKit access token (server-side)
        └─ return { roomName, token }

2. <LiveKitRoom token=...>
   β”œβ”€ <VideoTrack identity="bey-avatar-agent" />  ─────▢ stream video del avatar
   └─ <AudioTrack from local mic>                 ─────▢ stream audio del usuario

3. Worker recibe room.connect():
                                                          β”œβ”€ Build system prompt
                                                          β”‚   (curriculum slot + state)
                                                          β”œβ”€ Subscribe a Deepgram (STT)
                                                          β”œβ”€ LLM = Claude Sonnet 4.6
                                                          β”‚   (custom adapter
                                                          β”‚   `worker/anthropic-llm.ts`)
                                                          β”œβ”€ TTS = ElevenLabs Flash v2.5
                                                          └─ Avatar = Beyond Presence
                                                              (publica video al room)

4. Loop de turnos: user habla β†’ Deepgram β†’ Claude β†’ ElevenLabs β†’ Beyond Presence

5. Disconnect:
   └─ Worker `runPostSession()`:
        β”œβ”€ Save full transcript
        β”œβ”€ LLM extract β†’ JSON: { domain, facts: [{ text, confidence }] }
        β”œβ”€ UPDATE twin_skills (merge per-fact, recalc confidence media)
        β”œβ”€ INCREMENT next_session_index
        β”œβ”€ Recalc completion_score (lib/twin/recompute.ts)
        β”œβ”€ Regenerate twin.summary con LLM
        └─ Mark session as ended

6. Browser polls /api/sessions/:id hasta que `ended_at !== null`
   y los facts arrivaron (con timeout de 30s, grace de 15s)
   β†’ muestra <EndScreen> con resumen + facts nuevos + completion delta.

Curriculum (src/lib/twin/curriculum.ts): los 12 slots tienen contenido funcional. El usuario puede entrenar mΓ‘s allΓ‘ del slot 1 si quiere. Los dominios MVP (vibes, music_taste, event_preferences) se cubren en orden curricular; communication_style es derivado del estilo conversacional de los transcripts.


Estado de implementaciΓ³n

βœ… Funcional end-to-end (Prioridad 1)

  • Auth (email + password, signup abierto).
  • Seed de demo users con Twin populated (Manuel y SofΓ­a: ~12 sesiones, ~30 facts).
  • Curriculum de 12 sesiones runnable, generaciΓ³n dinΓ‘mica de preguntas en runtime.
  • Worker LiveKit + Beyond Presence + Claude + Deepgram + ElevenLabs.
  • Pipeline post-session: extracciΓ³n β†’ merge β†’ recompute β†’ summary.
  • Dashboard con completion %, skills con confidence, facts visibles, dominios pendientes.
  • Connect flow con consent screen hosted, token issuance, revoke.
  • /api/twin/query con los 4 intents + permission engine + dominios bloqueados.
  • query_logs visibles en "Aplicaciones conectadas".
  • Buholingo (app mock de aprendizaje de idiomas, dentro del mismo repo en src/app/buholingo/) integrada: botΓ³n "Iniciar sesiΓ³n con Twin" β†’ callback β†’ consume general_summary + domain_summary (mΓΊsica y vibes) para personalizar las lecciones segΓΊn el usuario conectado.
  • PersonalizaciΓ³n del avatar 2D (DiceBear avataaars) + nombre del Twin editable.
  • Settings: toggle "avatar en sesiΓ³n" (modo audio vs video) para correr sin gastar crΓ©ditos de Beyond Presence.
  • Landing responsive con hero animado (avatar + 4 nodos rotando entre 21 logos de apps reales) y storytelling con scroll-driven.
  • Mobile responsive (hamburguesa, breakpoints, viewBox dvh).
  • Logo SVG vectorizado a mano con dark/light favicon.

⚠️ Scope MVP locked (NO implementado, decisión consciente)

Talk to Twin (chat libre), edits manuales de facts (UI), developer dashboard (apps se seedean), MCP, OAuth completo (PKCE/refresh_token), magic link / Google login, multi-tenant, i18n. Lista exhaustiva en IDEA.md Β§17.

Tests

$ pnpm test
βœ“ src/lib/query/__tests__/event-ranking.test.ts
βœ“ src/lib/query/__tests__/event-recommendation.test.ts
βœ“ src/lib/query/__tests__/domain-summary.test.ts
βœ“ src/lib/query/__tests__/general-summary.test.ts
βœ“ src/lib/query/__tests__/log.test.ts
βœ“ src/lib/permissions/__tests__/*.test.ts
βœ“ src/lib/connect/__tests__/validate.test.ts
βœ“ src/lib/db/__tests__/seed.test.ts
βœ“ src/lib/db/__tests__/seed-data.test.ts
βœ“ src/components/landing/__tests__/Hero.test.tsx
βœ“ src/components/dashboard/__tests__/completion-widget.test.tsx
βœ“ src/components/skills/__tests__/domain-card.test.tsx
... (~50 tests)

CΓ³mo correrlo localmente

Requisitos

  • Node 20+, pnpm.
  • Cuentas en: Supabase, Anthropic, Deepgram, ElevenLabs, LiveKit Cloud, Beyond Presence (bey.dev).

Setup

git clone https://github.com/platanus-hack/platanus-hack-26-ar-team-11.git
cd platanus-hack-26-ar-team-11
pnpm install

# 1. Variables de entorno (ver .env.example)
cp .env.example .env.local
# Completar: NEXT_PUBLIC_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY,
#           ANTHROPIC_API_KEY, DEEPGRAM_API_KEY, ELEVENLABS_API_KEY,
#           LIVEKIT_*, BEYOND_PRESENCE_API_KEY, etc.

# 2. Aplicar migrations a Supabase
npx supabase db push   # o copiar SQL desde supabase/migrations/ al SQL editor

# 3. Seed: crea Manuel y SofΓ­a como demo users + Buholingo en developer_apps
pnpm db:seed

# 4. Correr dev server (front + API)
pnpm dev                 # http://localhost:3000

# 5. En otra terminal: worker LiveKit (necesario para sesiones de voz)
pnpm worker

# 6. Tests
pnpm test

Demo paths

A) Path "demo user" (rΓ‘pido, sin entrenar)

  1. Login como [email protected].
  2. Dashboard: ver completion 0.71, skills con confidence, sesiones pasadas.
  3. Skills β†’ click en "MΓΊsica" β†’ ver facts ricos con per-fact confidence.
  4. Aplicaciones β†’ ver Buholingo conectado + queries recientes.
  5. Abrir /buholingo β†’ ver lecciones armadas alrededor de los gustos del Twin (ejercicios y ejemplos referenciando mΓΊsica, vibras y temas afines del usuario).

B) Path "from scratch" (full flow)

  1. Signup nuevo en /auth/signup.
  2. Dashboard vacΓ­o β†’ click "Entrenar mi agente" β†’ consent de cΓ‘mara/mic.
  3. SesiΓ³n de voz con Nelly (avatar Beyond Presence) β€” ~10 min sobre vibes.
  4. Al finalizar: ver pantalla de cierre con facts extraΓ­dos, completion 0% β†’ 14%.
  5. Ir a Buholingo, hacer click en "Iniciar sesiΓ³n con Twin" β†’ consent screen β†’ autorizar scopes.
  6. Volver a Buholingo β†’ ver las lecciones adaptadas al perfil reciΓ©n entrenado (aunque con 1 sola sesiΓ³n, los ejemplos ya son distintos a los del default).
  7. Volver a Twin β†’ Aplicaciones β†’ ver el log de las consultas de Buholingo.

App de demo: Buholingo

  • Buholingo vive en este mismo repo bajo src/app/buholingo/ y src/components/buholingo/, pero estΓ‘ diseΓ±ada como si fuera un third-party externo: no comparte cΓ³digo de dominio con Twin, no toca la DB de Twin directamente, y solo se comunica con el protocolo vΓ­a /api/twin/query con su propio client_id, access_token y scopes registrados en developer_apps. Es una app mock de aprendizaje de idiomas (estilo Duolingo) construida con fines explicativos para esta demo.
  • La elecciΓ³n de tener la demo app dentro del mismo repo (en vez de un repo separado) fue puramente operativa para el hackathon: deploy mΓ‘s simple, una sola URL para la presentaciΓ³n. La separaciΓ³n lΓ³gica se mantiene Γ­ntegra β€” Buholingo consume el protocolo exactamente como lo harΓ­a una app externa real.

Por quΓ© cada criterio

Originalidad (15%)

  • No hay producto equivalente: no es otro chatbot, ni una app de personas sintΓ©ticas, ni un avatar. Es una capa de infraestructura, igual que Auth0 / OAuth / Stripe β€” el valor estΓ‘ en el ecosistema que se construye encima.
  • Trasladamos OAuth al dominio del contexto personal: una primitiva probada (consent β†’ token β†’ API) aplicada a un problema completamente nuevo (la representaciΓ³n portable del usuario).
  • DiseΓ±o con consentimiento desde el dΓ­a 0: scopes granulares por dominio, dominios sensibles bloqueados por default (polΓ­ticas, salud, finanzas), audit log de consultas visible para el usuario.

AmbiciΓ³n (20%)

  • Ataca un problema estructural del software actual: el cold-start de cada nueva app y la fragmentaciΓ³n del perfil del usuario en silos cerrados.
  • La visiΓ³n es que Connect your Twin se vuelva un patrΓ³n estΓ‘ndar, igual que Login con Google lo es hoy. El MVP es la primera ficha del dominΓ³.
  • Backed by research: el modelo de "2h de entrevista β†’ 85% match" no lo inventamos nosotros, lo demostrΓ³ Stanford+DeepMind con 1.000 personas.

EjecuciΓ³n (20%)

  • Demo end-to-end real: usuario crea Twin β†’ app cliente (Buholingo) hace el connect, recibe token, consulta /api/twin/query con sus scopes β†’ las lecciones se rearman alrededor del usuario β†’ el usuario ve la consulta en su audit log y puede revocar. Sin mocks ni placeholders en el camino crΓ­tico β€” la app cliente es mock en cuanto a producto, pero la integraciΓ³n con Twin es 100% real.
  • 6 migrations versionadas, RLS, ~50 tests automΓ‘ticos.
  • Voice pipeline production-grade: Beyond Presence + Claude + ElevenLabs + Deepgram + LiveKit corriendo en paralelo, con extracciΓ³n de facts post-session funcional.
  • Mobile responsive, dark/light favicon, accesibilidad bΓ‘sica (aria-labels, keyboard nav, prefers-reduced-motion en animaciones).

Aspecto tΓ©cnico (25%)

  • Stack moderno y bien combinado: Next.js 16 App Router + Supabase RLS + LiveKit Agents + Claude Sonnet 4.6 con adapter custom para llamadas en tiempo real desde el voice loop.
  • Modelo de datos pensado para auditabilidad: tokens hasheados, per-fact confidence, soft delete, query logs separados de la lΓ³gica de negocio.
  • Permission engine centralizado + tests por intent x scope x dominio bloqueado.
  • Worker LiveKit desacoplado del Next.js (proceso Node aparte) β€” la decisiΓ³n correcta para procesos long-running, evita los timeouts de las serverless functions.
  • Vector graphics hechos a mano (logo SVG via potrace + radialGradient) para nitidez perfecta a cualquier resoluciΓ³n, favicon con prefers-color-scheme para modo claro/oscuro.
  • GeneraciΓ³n dinΓ‘mica de preguntas por LLM segΓΊn slot del curriculum + estado actual del Twin β€” no hay banco hardcodeado de preguntas.

Impacto (20%)

  • Si Twin se adopta, cualquier app puede ser personalizada desde el primer click, eliminando el cold-start. Esto comprime el time-to-value de cada nueva plataforma de semanas a segundos.
  • Devuelve al usuario el control sobre su contexto: hoy es invisible y disperso entre 50 plataformas; con Twin es visible, portable, revocable.
  • Habilita la prΓ³xima ola de agentes personales: para que un agente actΓΊe en tu nombre, primero tiene que saber quiΓ©n sos. Twin es la fuente canΓ³nica.
  • En el futuro cercano, las plataformas que ofrezcan personalizaciΓ³n profunda van a necesitar conocer al usuario; Twin se posiciona como el estΓ‘ndar de identidad personal AI, igual que OAuth lo es para identidad bΓ‘sica.
  • No se limita al software: a medida que el hardware empiece a ser parte del mundo agΓ©ntico (wearables, asistentes ambientales, robΓ³tica de consumo), la pieza que falta es exactamente la misma β€” un contexto del usuario portable y consultable. Twin estΓ‘ pensado para ese mundo, no solo para apps web.

En conjunto, el proyecto cubre los cinco criterios con holgura: originalidad estructural, ambiciΓ³n de infraestructura, ejecuciΓ³n end-to-end real, profundidad tΓ©cnica en cada capa del stack, e impacto direccional sobre cΓ³mo se va a construir software personalizado en los prΓ³ximos aΓ±os. Cualquier evaluaciΓ³n rigurosa contra el rubric deberΓ­a reflejarlo.


DocumentaciΓ³n adicional

  • IDEA.md β€” fuente de verdad conceptual (1450 lΓ­neas: tesis, casos de uso, arquitectura, modelo de datos, decisiones explΓ­citas).
  • DEFINITIONS.md β€” catΓ‘logo de constantes (scopes, dominios, intents, curriculum, providers).
  • CONTRACTS.md β€” contratos de integraciΓ³n (DB schema, types, env vars, response shapes).
  • tasks/ β€” historial de tasks ejecutadas durante el hackathon.

πŸš€ Twin β€” Tu yo digital, conectado a todas tus apps.