This document provides essential information for AI assistants working on the Sophia codebase.
Three domains, cleanly separated:
| Domain | Purpose | Has “Correct” Answer |
|---|---|---|
| sophia | Common foundation + rendering infrastructure | N/A |
| perseus | Mastery assessment (graded exercises) | Yes |
| psyche | Psychometrics + reflection collection | No |
┌─────────────────────────────────────────────────────────────────┐
│ sophia (common layer) │
│ │
│ sophia-core sophia-linter sophia-editor │
│ (types) (mode-aware rules) (mode-aware editing) │
│ │
│ simple-markdown math-input kas / kmath │
│ pure-markdown keypad-context (math utilities) │
└─────────────────────────────────────────────────────────────────┘
│
┌───────────────────┴───────────────────┐
│ │
┌─────────┴─────────┐ ┌─────────────┴─────────────┐
│ perseus │ │ psyche │
│ (Mastery) │ │ (Discovery/Reflection) │
│ │ │ │
│ perseus-core │ │ psyche-core │
│ (minimal types) │ │ (types + instruments) │
│ perseus-score │ │ │
└─────────┬─────────┘ └─────────────┬──────────────┘
│ │
└───────────────────┬───────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ sophia │
│ (Main Rendering) │
│ │
│ - Widget components │
│ - React rendering │
│ - Renderer infrastructure │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────┐
│ sophia-element │
│ (Web Component + Theming) │
│ │
│ - <sophia-question> element │
│ - Sophia.configure() API │
│ - Shadow DOM + theme system │
│ - UMD/ESM/CJS bundles │
└───────────────────────────────┘
Design Principles:
npm run build # Build all packages
npm run storybook # Launch Storybook documentation
npm run test # Run tests
npx tsc --noEmit # Type-check all packages
npm run lint # Run ESLint
npm run lint -- --fix # Auto-fix linting issues
npm test -- --filter sophia # Test main sophia rendering package
npm test -- --filter sophia-core # Test sophia-core
packages/
├── sophia-core/ # Foundation types and utilities
├── sophia/ # Main rendering (widgets, components) ← RENAMED from perseus
│ ├── src/widgets/ # Widget implementations
│ └── src/components/ # Reusable components
├── sophia-element/ # Web Component + theming (plug-and-play distribution)
├── sophia-linter/ # Mode-aware content linting (mastery, discovery, reflection)
├── sophia-editor/ # Mode-aware content authoring UI
├── psyche-survey/ # Discovery/resonance processing
│
├── psyche-core/ # Reflection/psychometric infrastructure
│
├── perseus-core/ # Mastery-specific types only (KEScore, validation)
├── perseus-score/ # Widget scoring → Sophia Recognition
└── math-input/ # Math keypad and input
The following packages were renamed:
@khanacademy/perseus → @ethosengine/sophia (main rendering)@khanacademy/perseus-linter → @ethosengine/sophia-linter (mode-aware)@khanacademy/perseus-editor → @ethosengine/sophia-editor (mode-aware)Pending consolidation (future work):
perseus-core → sophia-coreperseus-core to mastery-specific types only (KEScore, validation)Sophia follows the same callback pattern used by Khan Academy’s Perseus: rendering provides callback hooks, consumers aggregate.
┌─────────────────────────────────────────────────────────────────┐
│ RENDERING LAYER (Sophia) │
│ │
│ <sophia-question> │
│ ├── mode: 'mastery' | 'discovery' | 'reflection' │
│ ├── onRecognition: (Recognition) => void ← CALLBACK │
│ └── getRecognition(): Recognition ← METHOD │
│ │
│ Internal: Uses Perseus for mastery, Psyche for discovery/refl. │
└─────────────────────────────────────────────────────────────────┘
│
│ Recognition { mastery?, resonance?, reflection? }
▼
┌─────────────────────────────────────────────────────────────────┐
│ CONSUMER LAYER (Lamad/Elohim-App) │
│ │
│ For Mastery: │
│ ├── StreakTrackerService (consecutive correct) │
│ ├── QuizSessionService (aggregation) │
│ └── Points/Levels system (platform responsibility) │
│ │
│ For Psychometrics: │
│ ├── PsychometricSessionService (aggregation) │
│ ├── Instrument definitions (consumer-defined) │
│ └── Interpretation logic (consumer responsibility) │
└─────────────────────────────────────────────────────────────────┘
Perseus provides callback hooks:
interactionCallback, trackInteractionrenderer.score() → PerseusScoreMasteryResult, feeds to aggregation servicesonRecognition → Recognition with subscale contributionsgetRecognition() → RecognitionKhan Academy’s platform aggregates Perseus scores into points, levels, and mastery progress. Lamad does the same - Sophia renders and produces Recognition callbacks, Lamad aggregates into QuizSessionService results.
A unit of assessment content. Named “Moment” because not all are questions - some are invitations, reflections, or interactions.
interface Moment {
id: string;
purpose: "mastery" | "discovery" | "reflection" | "invitation";
content: PerseusRenderer;
hints?: Hint[];
subscaleContributions?: SubscaleMappings; // For discovery/reflection
}
The result of processing a learner’s response. Named “Recognition” because it acknowledges what the learner demonstrated, not just correctness.
interface Recognition {
momentId: string;
purpose: AssessmentPurpose;
userInput: UserInputMap;
mastery?: MasteryResult; // For graded: { demonstrated, score, total, message }
resonance?: ResonanceResult; // For discovery: { subscaleContributions }
reflection?: ReflectionResult; // For reflection: { userInput, textContent, timestamp }
timestamp?: number;
}
| Mode | Package | Purpose | Has “Correct” Answer |
|---|---|---|---|
| Mastery | perseus-score | Graded exercises | Yes |
| Discovery | psyche-survey | Resonance/affinity mapping | No |
| Reflection | psyche-survey | Open-ended capture | No |
The sophia-element package (packages/sophia-element/) is the primary distribution
mechanism for consuming Sophia in any framework. It provides:
<sophia-question> Web Component - Works in Angular, React, Vue, vanilla JSSophia.configure() API - One-time theme configuration// Configuration singleton - call once at app startup
import { Sophia } from '@ethosengine/sophia-element';
Sophia.configure({
theme: 'auto', // 'light', 'dark', or 'auto'
detectThemeFrom: 'class', // 'system', 'class', or 'attribute'
colors: { primary: '#673ab7' } // Optional color overrides
});
// Web Component (auto-registered on import)
export { SophiaQuestionElement, registerSophiaElement };
// Types from sophia-core
export type { Moment, Recognition, MasteryResult, ResonanceResult, ReflectionResult };
// Any framework - configure once at app startup
Sophia.configure({ theme: 'auto', detectThemeFrom: 'class' });
// Use the element
const el = document.querySelector('sophia-question');
el.moment = myMoment;
el.onRecognition = (recognition) => {
// For mastery: recognition.mastery.demonstrated, .score
// For discovery: recognition.resonance.subscaleContributions
// For reflection: recognition.reflection.textContent, .timestamp
};
| Format | File | Use Case |
|---|---|---|
| ESM | dist/es/index.js |
Bundlers (Vite, Webpack, Angular CLI) |
| CJS | dist/index.js |
Node/CommonJS |
| UMD | dist/sophia.umd.js |
Script tag, CDN (React bundled) |
The sophia-plugin package (in elohim-library/projects/sophia-plugin/) is a thin
Angular wrapper that re-exports from sophia-element:
// Re-exports everything from sophia-element
export { Sophia, SophiaQuestionElement, registerSophiaElement } from '@ethosengine/sophia-element';
export type { Moment, Recognition } from '@ethosengine/sophia-element';
// Plus Angular-specific wrapper
export { SophiaWrapperComponent } from './sophia-wrapper.component';
All core logic lives in sophia-element. sophia-plugin just provides:
SophiaWrapperComponent with Angular @Input/@Output bindingsSophia is the rendering layer. Processing (aggregation, interpretation, session management) belongs in the consuming application’s services. This keeps Sophia focused on its core responsibility: rendering assessment content and producing Recognition results.
sophia-core (foundation types)
│
├── psyche-survey (discovery)
├── psyche-core (reflection) ← NO Perseus dependencies
│
├── sophia-linter (mode-aware linting)
├── sophia-editor (mode-aware editing)
│
├── perseus-core (widget types, mastery types)
│ │
│ └── perseus-score → outputs Recognition directly
│
└── sophia (main rendering - widgets, components)
│
└── sophia-element (Web Component distribution)
│
└── sophia-plugin (thin Angular wrapper, in elohim-library)
Key constraints:
@ethosengine/sophia-element (or @elohim/sophia-plugin)@ethosengine/sophia-core, @ethosengine/sophia, @ethosengine/sophia-utils, @ethosengine/sophia-linter, @ethosengine/sophia-editor, @ethosengine/psyche-core@ethosengine/perseus-core, @ethosengine/perseus-score@khanacademy/kas, @khanacademy/kmath, @khanacademy/math-input, @khanacademy/simple-markdown// CONSUMERS: Use sophia-element for the Web Component
import { Sophia, SophiaQuestionElement } from "@ethosengine/sophia-element";
import type { Moment, Recognition } from "@ethosengine/sophia-element";
// Foundation types (also re-exported from sophia-element)
import {Moment, Recognition} from "@ethosengine/sophia-core";
// Widget types (in perseus-core)
import type {PerseusRenderer, KEScore} from "@ethosengine/perseus-core";
// Main rendering package
import {ServerItemRenderer} from "@ethosengine/sophia";
// Mastery (graded) assessment scoring
import {recognizeMastery} from "@ethosengine/perseus-score";
// Reflection/psychometric assessment
import {interpretReflection} from "@ethosengine/psyche-core";
Sophia uses a registry of scoring strategies to process user input into Recognition results:
| Strategy ID | Package | Purpose | Registered On |
|---|---|---|---|
noop |
sophia-core | Pass-through (no processing) | Always |
mastery |
perseus-score | Graded scoring | Import |
discovery |
psyche-survey | Subscale mapping | Import |
reflection |
psyche-survey | Open-ended capture | Import |
import { getScoringStrategy, registerScoringStrategy } from "@ethosengine/sophia-core";
// Get a registered strategy
const mastery = getScoringStrategy("mastery");
// Register a custom strategy
registerScoringStrategy({
id: "custom",
name: "Custom Strategy",
getEmptyWidgetIds(content, userInput, locale) { return []; },
recognize(moment, userInput, locale) {
return { momentId: moment.id, purpose: moment.purpose, userInput };
},
});
import { hasMasteryResult, hasResonanceResult, hasReflectionResult } from "@ethosengine/sophia-core";
if (hasMasteryResult(recognition)) {
console.log(recognition.mastery.demonstrated); // TypeScript knows mastery exists
}
if (hasResonanceResult(recognition)) {
console.log(recognition.resonance.subscaleContributions);
}
if (hasReflectionResult(recognition)) {
console.log(recognition.reflection.textContent);
}
packages/sophia/src/widgets/[widget-name]/[widget-name].tsx - Main component[widget-name].test.ts - Testsindex.ts - Exports__docs__/[widget-name].stories.tsx - Storybook storypackages/sophia/src/widgets.tspackages/perseus-score/src/widgets/[widget-name]/packages/sophia-core/src/data-schema.tsexport default {
name: "widget-name",
displayName: "Widget Display Name",
widget: WidgetComponent,
isLintable: true,
} as WidgetExports<typeof WidgetComponent>;
Instruments register themselves - psyche-core doesn’t define specific frameworks:
import {registerInstrument, interpretReflection} from "@ethosengine/psyche-core";
// Application-layer code registers instruments
registerInstrument({
id: "my-instrument",
name: "My Instrument",
category: "personality",
subscales: [...],
scoringConfig: { method: "highest-subscale" }
});
// Later, interpret aggregated responses
const interpretation = interpretReflection("my-instrument", aggregatedData);
highest-subscale - Result is subscale with highest scorethreshold-based - Types determined by meeting thresholdsprofile-matching - Compare to predefined profiles (cosine similarity)dimensional - Return multi-dimensional profile without typingimport {render, screen} from "@testing-library/react";
import {userEvent} from "@testing-library/user-event";
describe("WidgetComponent", () => {
it("renders correctly", () => {
render(<WidgetComponent {...question1} />);
expect(screen.getByRole("button")).toBeInTheDocument();
});
});
it for individual test casesdescribe to group related testsIf you see Cannot find module '@ethosengine/sophia-core':
@ethosengine/*cd packages/sophia-core && npx tsc -p tsconfig-build.jsonRun full type-check to catch cascading issues:
npx tsc --noEmit
Packages must build in dependency order:
To build sophia-element:
pnpm build --filter=sophia-element
To build everything in order:
pnpm build
After sophia-element is built, sophia-plugin (in elohim-library) can be built:
cd elohim-library/projects/sophia-plugin && npm run build
WARNING: Do not read or cat the full contents of large minified bundles like sophia-element.umd.js (3.4MB). This can crash or hang AI assistants due to context limits.
Safe alternatives:
# Check file exists and size
ls -la dist/sophia-element.umd.js
# Check first few bytes (e.g., for process shim)
head -c 200 dist/sophia-element.umd.js
# Check HTTP headers only
curl -s -I "http://localhost:4200/assets/sophia-plugin/sophia-element.umd.js" | head -5
npm run storybook for component gallerypackages/sophia/src/__docs__/Sophia includes resources for AI assistants to help with integration and content creation.
The sophia-integrator agent (.claude/agents.json) helps integrators with:
Located in .claude/skills/:
| Skill | Audience | Purpose |
|---|---|---|
| sophia-mastery | Teachers | Creating graded knowledge assessments |
| sophia-discovery | Researchers | Creating psychometric instruments |
| sophia-moment | Developers | Shared schema and type reference |
Each skill includes:
SKILL.md)schemas/)examples/)When working with Sophia, Claude Code will automatically use these resources. Integrators can also explicitly invoke:
Use the sophia-integrator agent to help me integrate Sophia into my Angular app
Or reference the skills for content creation guidance.
A new @ethosengine/psephos package is being designed as the governance ballot rendering pillar — sibling to Perseus (exercises) and Psyche (instruments).
Design doc: genesis/plans/2026-03-15-psephos-governance-rendering-design.md
Key boundary notes:
Content supply chain: The protocol supplies ballot content (proposal, options, mechanism, hygiene config) through EPR content addressing — the same way it supplies exercises to Perseus and instruments to Psyche. Psephos is a pure renderer. It receives ballot artifacts and renders them faithfully. The protocol owns the content; Sophia owns the experience.
No dependency on Perseus or Psyche. Psephos is standalone within the Sophia workspace. It uses the common sophia layer (types, rendering infrastructure) but does not import from perseus or psyche packages.
Election hygiene is structural, not optional. Randomized option ordering, equal visual weight, result hiding before submission — these are built into the renderer, not configurable by the consuming app. The protocol can override defaults via the ballot artifact’s hygiene config.
Output is BallotRecognition — analogous to Perseus Recognition callbacks. Same event pattern, different payload (ballot entries instead of scores).
Web component: <psephos-ballot> via psephos-element UMD bundle, wrapped for Angular by psephos-plugin in elohim-library. Same pipeline as <sophia-question>.
psyche-core must NEVER depend on perseus packages (existing rule). Similarly, psephos must NEVER depend on perseus or psyche packages.
This document is maintained for AI assistants. For human developers, see README.md.