Skip to content

Latest commit

 

History

History
435 lines (331 loc) · 12.8 KB

File metadata and controls

435 lines (331 loc) · 12.8 KB

Lucid SDK — API Reference

Complete reference for all public methods, types, and configuration options.


Lucid Class

The main entry point for the SDK.

Constructor

import OpenAI from 'openai';
import { Lucid, MemoryStore, LLMAnalyzer } from '@tetiai/lucid';

const client = new OpenAI({
  apiKey: process.env.LUCID_API_KEY,
  baseURL: process.env.LUCID_BASE_URL,
});

const lucid = new Lucid({
  store: new MemoryStore(),                              // Required
  analyzer: new LLMAnalyzer({ client, model: process.env.LUCID_MODEL }), // Optional
  debug: true,                                           // Optional (default: false)
});
Option Type Required Description
store LucidStore Yes Storage adapter for persisting cognitive data
analyzer LucidAnalyzer No LLM analyzer for automatic scoring
debug boolean No Enable console logging (default: false)

lucid.track(userId, input)

Track a user-AI message exchange with automatic LLM analysis.

await lucid.track('user-123', {
  userMessage: "Why does this algorithm use O(n log n)?",
  aiResponse: "The algorithm uses a divide-and-conquer approach...",
  topicId: "conversation-456",  // optional
});
Parameter Type Required Description
userId string Yes Unique user identifier from your system
input.userMessage string Yes The user's message text
input.aiResponse string Yes The AI's response text
input.topicId string No Conversation/topic ID for per-conversation tracking

Returns: Promise<void>

Behavior:

  • Skips messages shorter than 5 characters
  • Calls the configured analyzer to score the message
  • Updates topic cognitive data (if topicId provided)
  • Updates user cognitive data (session, scores, level)
  • Fire-and-forget safe: errors are logged, not thrown

lucid.getProfile(userId)

Get the public cognitive profile for a user.

const profile = await lucid.getProfile('user-123');

// Returns:
{
  userId: 'user-123',
  score: 3200,              // 0-10000
  level: 3,                 // 0-10
  levelProgress: 0.2,       // progress to next level (0-1)
  trend: 'improving',       // 'improving' | 'stable' | 'declining'
  autonomy: 0.45,           // 0 to 1 (EMA smoothed)
  learning: 0.72,           // 0-1 (EMA smoothed)
  engagement: 0.65,         // 0-1 (EMA smoothed)
  metacognition: 0.58,      // 0-1 (EMA smoothed)
  verification: 0.42,       // 0-1 (EMA smoothed)
  dominantMotivation: 'instrumental', // 'intrinsic' | 'instrumental' | 'avoidance'
  trustCalibration: 'calibrated',     // 'calibrated' | 'over_trust' | 'under_trust'
  totalMessages: 142,
  isFatigued: false,
  fatigueReason: undefined,
  driftIndex: 0.12,         // 0-1 (Cognitive Drift Index)
  scaffoldingLevel: 'guided', // 'full' | 'guided' | 'hints' | 'challenge'
  weeklyHistory: [
    { week: '2026-W10', autonomy: 0.4, learning: 0.7, metacognition: 0.5, messages: 120, fatigue_events: 2 },
    { week: '2026-W11', autonomy: 0.45, learning: 0.72, metacognition: 0.58, messages: 142, fatigue_events: 1 },
  ]
}

Returns: Promise<CognitiveProfile | null> — null if no data tracked yet


lucid.getGuidelines(userId | data, ageGroup?)

Get cognitive adaptation guidelines for injection into an AI system prompt. Pass ageGroup to enable age-based protections.

You can pass either a userId (Lucid loads the profile from the store) or a UserCognitiveData object directly (skips the store query).

// With userId — Lucid loads from store
const guidelines = await lucid.getGuidelines('user-123', 'teen');

// With profile — no extra store query
const profile = await lucid.getProfile('user-123');
const guidelines = await lucid.getGuidelines(profile, 'child');
// Returns a text block like:
//
// ## COGNITIVE SUPPORT
// Adapt your responses to protect and enhance user cognition:
//
// User tends to delegate (autonomy: 0.25)
// - ALWAYS ask for their initial thoughts before providing solutions
// ...

// Usage:
const systemPrompt = baseSystemPrompt + guidelines;

Returns: Promise<string> — empty string if no cognitive data


lucid.getTopicData(topicId)

Get cognitive data for a specific conversation/topic.

const topicData = await lucid.getTopicData('conversation-456');

Returns: Promise<TopicCognitiveData | null>


lucid.deleteUser(userId)

Delete all cognitive data for a user — profile, all topics, moderation data. For GDPR compliance or account deletion.

await lucid.deleteUser('user-123');

Returns: Promise<void>


lucid.deleteTopic(topicId)

Delete all cognitive and moderation data for a specific topic/conversation.

await lucid.deleteTopic('conversation-456');

Returns: Promise<void>


Store Adapters

MemoryStore

In-memory storage for development and testing.

import { MemoryStore } from '@tetiai/lucid';

const store = new MemoryStore();

// Additional method for testing:
store.clear(); // Wipe all data

SqliteStore

Zero-dependency local persistence using Node's built-in node:sqlite (requires Node.js >= 22.5). No external packages, no server — data lives in a single file. Recommended for local development and single-node self-hosting. userId and topicId are stored as real indexed columns, so lookups and countUserTopics are portable and fast.

import { SqliteStore } from '@tetiai/lucid';

const store = new SqliteStore('lucid.db');   // persistent file
// const store = new SqliteStore(':memory:'); // ephemeral (tests)

// Release the file handle when done:
await store.close();

PostgresStore

Postgres storage using a raw pg client — no ORM, no Prisma. Bring your own Pool or Client. Tables are created on first use. userId/topicId are real indexed columns, so no JSON-path filters are needed.

import { PostgresStore } from '@tetiai/lucid';
import { Pool } from 'pg';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const store = new PostgresStore(pool);

MysqlStore

MySQL storage using a raw mysql2 pool/connection — no ORM, no Prisma. Use the promise API (mysql2/promise).

import { MysqlStore } from '@tetiai/lucid';
import mysql from 'mysql2/promise';

const pool = mysql.createPool(process.env.DATABASE_URL);
const store = new MysqlStore(pool);

PrismaStore

Postgres / MySQL storage via Prisma ORM — optional, for stacks that already use Prisma. Reuses your existing connection.

Targets Postgres and MySQL. It uses JSON-path filters that SQLite and MongoDB don't support portably — for SQLite use SqliteStore, or use PostgresStore / MysqlStore to skip the ORM entirely.

import { PrismaStore } from '@tetiai/lucid';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();
const store = new PrismaStore(prisma);

Requirements:

  • Add the LucidCognitive model to your Prisma schema (see prisma/schema.prisma)
  • Run npx prisma db push or create a migration

Custom Store

Implement the LucidStore interface for any database:

import type { LucidStore } from '@tetiai/lucid';

class MyCustomStore implements LucidStore {
  async getUser(userId: string) { /* ... */ }
  async saveUser(userId: string, data: UserCognitiveData) { /* ... */ }
  async getTopic(topicId: string) { /* ... */ }
  async saveTopic(topicId: string, data: TopicCognitiveData) { /* ... */ }
  async getTopicModeration(topicId: string) { /* ... */ }
  async saveTopicModeration(topicId: string, data: TopicModerationData) { /* ... */ }
  async getUserModeration(userId: string) { /* ... */ }
  async saveUserModeration(userId: string, data: UserModerationData) { /* ... */ }
  async countUserTopics(userId: string) { /* ... */ }
}

Analyzers

LLMAnalyzer

Uses the openai SDK as a universal client. Works with any compatible provider (Together AI, Groq, Ollama, OpenRouter) via baseURL.

import OpenAI from 'openai';
import { LLMAnalyzer } from '@tetiai/lucid';

// Any OpenAI-compatible provider:
// Together AI → baseURL: 'https://api.together.xyz/v1'
// Groq        → baseURL: 'https://api.groq.com/openai/v1'
// Ollama      → baseURL: 'http://localhost:11434/v1'
// OpenRouter  → baseURL: 'https://openrouter.ai/api/v1'
const client = new OpenAI({
  apiKey: process.env.LUCID_API_KEY,
  baseURL: process.env.LUCID_BASE_URL,
});

const analyzer = new LLMAnalyzer({
  client,                                  // Required: OpenAI-compatible client
  model: process.env.LUCID_MODEL,          // Required: set via env
  temperature: 0.1,                        // default
  maxTokens: 600,                          // default
  maxAiResponseLength: 2000,               // default, truncates AI response
});

Custom Analyzer

Implement the LucidAnalyzer interface for non-compatible providers:

import type { LucidAnalyzer, AnalysisResult } from '@tetiai/lucid';

class MyAnalyzer implements LucidAnalyzer {
  async analyze(userMessage: string, aiResponse: string): Promise<AnalysisResult | null> {
    // Call your LLM, return scores
    return {
      summary: "...",
      cognitive: {
        autonomy_score: 0.5,       // 0 to 1
        learning_score: 0.8,        // 0 to 1
        engagement_score: 0.6,      // 0 to 1
        metacognition_score: 0.7,   // 0 to 1
        verification_score: 0.4,    // 0 to 1
        motivation_type: 'intrinsic', // 'intrinsic' | 'instrumental' | 'avoidance'
        delegation_count: 0,
        delegation_type: 'none',    // 'none' | 'routine' | 'cognitive'
        learning_moments: 1,
        application_moments: 0,
      },
      moderation: { has_flags: false, flags: [] },
    };
  }
}

Scoring Utilities

All scoring functions are exported for custom implementations:

import {
  getAdaptiveAlpha,     // (totalMessages) → alpha
  calculateEMA,         // (current, previous, alpha) → smoothed
  calculateScore,       // (autonomy, learning, engagement, messages) → 0-10000
  calculateLevel,       // (score) → 0-10
  getLevelProgress,     // (score) → 0-1
  calculateTrend,       // (current, previous, prevTrend?) → trend
  calculateQualityScore, // (autonomy, learning, engagement) → 0-1
  calculateExperienceFactor, // (totalMessages) → 0-1
  getScaffoldingLevel,  // (autonomy, learning, metacognition) → 'full' | 'guided' | 'hints' | 'challenge'
  getTrustCalibration,  // (avgVerification) → 'calibrated' | 'over_trust' | 'under_trust'
  getDominantMotivation, // (counts) → 'intrinsic' | 'instrumental' | 'avoidance'
  clamp,                // (value, min, max) → clamped
  getWeekString,        // (date) → "2026-W11"
} from '@tetiai/lucid';

Session Utilities

import {
  detectFatigue,      // (session, now, ageGroup?) → { is_fatigued, reason? }
  updateSession,      // (existing, msgLength, now, ageGroup?) → SessionData
  createEmptySession, // (now?) → SessionData
} from '@tetiai/lucid';

Drift Utilities

import {
  calculateDriftIndex, // (weeklyHistory) → 0-1 (Cognitive Drift Index)
} from '@tetiai/lucid';

Constants

import {
  MIN_LEVEL,              // 0
  MAX_COGNITIVE_LEVEL,    // 10
  EMA_ALPHA_BASE,         // 0.1
  EMA_ALPHA_MIN,          // 0.02
  EXPERIENCE_MESSAGES_MAX, // 1000
  MAX_SCORE,              // 10000
  POINTS_PER_LEVEL,       // 1000
  MAX_LEVEL,              // 10
  QUALITY_WEIGHTS,        // { autonomy: 0.30, learning: 0.40, engagement: 0.30 }
  SESSION_GAP_MINUTES,    // 30
  FATIGUE_SESSION_MINUTES,              // 45 (adults)
  FATIGUE_SESSION_MINUTES_YOUNG_ADULT,  // 30 (18-24)
  FATIGUE_SESSION_MINUTES_TEEN,         // 20 (13-17)
  FATIGUE_SESSION_MINUTES_CHILD,        // 15 (6-12)
  FATIGUE_MESSAGE_COUNT,                // 30 (adults)
  FATIGUE_MESSAGE_COUNT_YOUNG_ADULT,    // 20 (18-24)
  FATIGUE_MESSAGE_COUNT_TEEN,           // 15 (13-17)
  FATIGUE_MESSAGE_COUNT_CHILD,          // 10 (6-12)
} from '@tetiai/lucid';

Types

All TypeScript types are exported:

import type {
  LucidConfig,
  LucidStore,
  LucidAnalyzer,
  TrackInput,
  CognitiveProfile,
  UserCognitiveData,
  TopicCognitiveData,
  TopicModerationData,
  UserModerationData,
  AnalysisResult,
  TrackRecord,
  ModerationFlag,
  SessionData,
  WeeklySnapshot,
  AgeGroup,
  DelegationType,           // 'none' | 'routine' | 'cognitive'
  MotivationType,           // 'intrinsic' | 'instrumental' | 'avoidance'
  TrustCalibration,         // 'calibrated' | 'over_trust' | 'under_trust'
  ScaffoldingLevel,         // 'full' | 'guided' | 'hints' | 'challenge'
} from '@tetiai/lucid';

See src/types.ts for full type definitions.