Chronicle Backend - Supermemory Integration
A comprehensive backend system for managing player personas across multiple games, genres, and platforms using Supermemory API for persistent storage.
Table of Contents
- Overview
- Architecture
- Data Models
- API Endpoints
- Persona Scopes
- Trait Computation
- Setup & Configuration
- Usage Examples
- Testing
Overview
This system captures player gameplay data, computes personality traits, and stores them as personas in Supermemory. Personas are maintained at multiple scopes:
- Global: Across all games
- Game: Specific to a game
- Genre: Across games in the same genre
- Platform: Across games on the same platform
Traits are automatically blended when new gameplay data is received, creating evolving player profiles.
Architecture
Game Client
↓ (POST /sm/save)
Express Server (server.ts)
↓
SupermemoryStore (memory/SupermemoryStore.ts)
↓
traitEngine (traitEngine.ts)
↓
Supermemory API (v3)
↓
Persona Documents
Key Components
- server.ts: Express server with API routes
- SupermemoryStore.ts: Handles all Supermemory API interactions
- traitEngine.ts: Computes traits from gameplay stats
- types.ts: Zod schemas and TypeScript types
Data Models
ServerInput
The primary input format sent from game clients to the backend. This represents a complete gameplay run/session.
Purpose: Captures all gameplay data needed to compute player traits and update personas across different scopes (global, game, genre, platform).
When to send: After each completed gameplay run or session.
type ServerInput = {
// Metadata
schema_version: string; // Version of the schema (e.g., "1.0")
player_id: string; // Unique player identifier
session_id: string; // Groups runs in one play session
run_index: number; // Sequential run number (1, 2, 3...)
completed_at: string; // ISO 8601 timestamp when run completed
// Game Context
game_context?: {
game_id?: string; // Canonical game identifier
game_title?: string; // Human-readable game name
genre_ids?: string[]; // Array of genre identifiers
platform_ids?: string[]; // Array of platform identifiers
build_version?: string; // Game build version
};
// Run Outcome
run_outcome: {
result: "win" | "loss"; // Run result
path: "combat" | "puzzle" | "exploration"; // Primary playstyle path
};
// Gameplay Statistics
stats: {
time_s: number; // Total time in seconds
deaths: number; // Number of deaths
retries: number; // Number of retries
distance_traveled: number; // Distance traveled (units)
jumps: number; // Number of jumps
hint_offers: number; // Number of hints offered
hints_used: number; // Number of hints actually used
riddles_attempted: number; // Total riddles attempted
riddles_correct: number; // Number of riddles solved correctly
combats_initiated: number; // Number of combat encounters started
combats_won: number; // Number of combats won
collectibles_found: number; // Number of collectibles collected
};
// Configuration
config_used: {
mode: "fun" | "challenge"; // Game mode
knobs: {
enemy_count: number;
enemy_speed: number;
puzzle_gate_ratio: number;
collectible_density: number;
hint_delay_ms: number;
breadcrumb_brightness: number;
};
layout_seed: string; // Random seed for level generation
};
// Optional fields
events_digest?: Array<{ // Event frequency summary
type: string; // Event type identifier (e.g., "combat.start")
count: number; // Number of times this event occurred
}>;
performance_summary?: unknown; // Additional performance metrics (future use)
};
Field Descriptions:
schema_version: Version of the data schema. Currently"1.0". Used for future schema migrations.player_id: Unique identifier for the player. Used to group all personas for a single player.session_id: Groups multiple runs that occur in the same play session. Can be any unique string per session.run_index: Sequential number of this run (1, 2, 3...). Used for ordering runs and tracking progress.completed_at: ISO 8601 timestamp when the run completed. Used for chronological ordering.
Game Context:
game_id: Identifier for the game being played. Enables game-specific personas.genre_ids: Array of genre identifiers. Only the first genre is used for persona creation.platform_ids: Array of platform identifiers. Only the first platform is used for persona creation.game_title: Human-readable game name (optional, for display purposes).build_version: Game build version (optional, for tracking).
Run Outcome:
result: Whether the player won ("win") or lost ("loss").path: Primary playstyle path taken:"combat","puzzle", or"exploration".
Stats (All Required):
These numbers feed directly into trait computation. All must be provided (use 0 if not applicable):
time_s: Total time in seconds (used for goal_focus and resilience).deaths: Number of deaths (lowers stealth, affects resilience).retries: Number of retries (affects resilience and goal_focus).distance_traveled: Distance covered (used for curiosity computation).jumps: Number of jumps (optional metric, currently not used in trait computation).mashing_intensity: Button mashing speed/intensity (0.0-1.0, optional). Higher values indicate faster, more intense button pressing. Affectsaggressionandgoal_focustraits.hint_offers: Number of hints offered to player (used with hints_used for independence).hints_used: Number of hints actually used (lowers independence trait).riddles_attempted: Total riddles encountered (used for puzzle_affinity).riddles_correct: Riddles solved correctly (primary input for puzzle_affinity).combats_initiated: Combat encounters started (primary input for aggression).combats_won: Combats won (contributes to aggression, affects resilience).collectibles_found: Collectibles collected (primary input for curiosity).
Configuration:
mode: Game difficulty mode ("fun"or"challenge").knobs: Game configuration parameters (currently stored but not used in trait computation).layout_seed: Random seed for level generation (used for reproducibility).
Example with Real Data:
{
"schema_version": "1.0",
"player_id": "user_1234",
"session_id": "sess_abc123",
"run_index": 3,
"completed_at": "2025-11-01T19:13:00Z",
"game_context": {
"game_id": "skyline_runner",
"game_title": "Skyline Runner",
"genre_ids": ["platformer", "action"],
"platform_ids": ["pc"],
"build_version": "v1.2.0"
},
"run_outcome": {
"result": "win",
"path": "combat"
},
"stats": {
"time_s": 215,
"deaths": 2,
"retries": 2,
"distance_traveled": 610,
"jumps": 21,
"hint_offers": 2,
"hints_used": 0,
"riddles_attempted": 3,
"riddles_correct": 2,
"combats_initiated": 8,
"combats_won": 6,
"collectibles_found": 5
},
"config_used": {
"mode": "challenge",
"knobs": {
"enemy_count": 3,
"enemy_speed": 1.3,
"puzzle_gate_ratio": 0.4,
"collectible_density": 0.25,
"hint_delay_ms": 12000,
"breadcrumb_brightness": 0.35
},
"layout_seed": "seed_6d83da"
},
"events_digest": [
{ "type": "combat.start", "count": 8 },
{ "type": "combat.win", "count": 6 },
{ "type": "death", "count": 2 },
{ "type": "fail.retry", "count": 2 }
]
}
Stats
Core gameplay statistics used for trait computation.
type Stats = {
time_s: number; // Time spent in seconds (required)
deaths: number; // Death count (required)
retries: number; // Retry count (required)
distance_traveled: number; // Distance traveled (required)
jumps: number; // Jump count (required)
hint_offers: number; // Hints offered (required)
hints_used: number; // Hints used (required)
riddles_attempted: number; // Riddles attempted (required)
riddles_correct: number; // Riddles solved correctly (required)
combats_initiated: number; // Combats started (required)
combats_won: number; // Combats won (required)
collectibles_found: number; // Collectibles found (required)
mashing_intensity?: number; // Button mashing speed (0.0-1.0, optional)
};
Traits
Normalized personality traits (0.0 to 1.0) computed from gameplay stats.
type Traits = {
aggression: number; // Combat preference (0.0-1.0)
stealth: number; // Avoidance preference (0.0-1.0)
curiosity: number; // Exploration tendency (0.0-1.0)
puzzle_affinity: number; // Puzzle-solving preference (0.0-1.0)
independence: number; // Hint usage aversion (0.0-1.0)
resilience: number; // Failure recovery (0.0-1.0)
goal_focus: number; // Completion efficiency (0.0-1.0)
};
PersonaSnapshot
Complete persona representation stored in Supermemory.
type PersonaSnapshot = {
player_id: string; // Player identifier
traits: Traits; // Computed traits
persona_text: string; // Human-readable description (1-3 sentences)
top_signals: string[]; // Top 3 gameplay signals (e.g., "Solved 5 riddle(s)")
updated_at: string; // ISO 8601 timestamp of last update
};
API Response Types
Save Response
type SaveResponse = {
user_node: string; // User node identifier
memories_created: number; // Total number of trait memories created
batch_result: {
results: Array<{
id: string; // Supermemory document ID
status: "queued" | "done" | "failed";
}>;
failed: number;
success: number;
};
trait_explanations: string[]; // Array of explanations showing how stats affected each trait
// Example: [
// "Aggression: increased from 0.50 to 0.42. Affected by: Started 1 combat(s), won 1 combat(s).",
// "Curiosity: decreased from 0.50 to 0.31. Affected by: traveled 300 units.",
// "Goal Focus: increased from 0.50 to 0.59. Affected by: fast completion (143s), no retries needed.",
// ...
// ]
// Each explanation shows: trait name, change direction, previous value, new value, and contributing stats
};
Fetch Response
type FetchResponse = {
total: number; // Total matching documents
items: Array<{
id: string; // Supermemory document ID
metadata: {
type: "persona";
persona_scope: "global" | "game" | "genre" | "platform";
player_id: string;
updated_at: string;
run_index: number; // Latest run index
run_result: "win" | "loss";
run_path: "combat" | "puzzle" | "exploration";
game_id?: string;
genre_id?: string;
platform_id?: string;
completed_at: string;
build_version?: string;
game_title?: string;
};
persona: PersonaSnapshot | null; // null if includeContent=false
}>;
};
API Endpoints
GET /health
Health check endpoint to verify the server is running.
Request:
GET /health
Response: 200 OK
{
"status": "ok",
"timestamp": "2025-11-02T11:01:52.855Z"
}
POST /sm/save
Saves gameplay data and updates/creates personas.
Request:
POST /sm/save
Content-Type: application/json
{
"serverInput": ServerInput
}
Request Body:
The serverInput field must contain a complete ServerInput object. See Data Models - ServerInput section for full structure.
Minimal Example:
{
"serverInput": {
"schema_version": "1.0",
"player_id": "player_123",
"session_id": "sess_abc",
"run_index": 1,
"completed_at": "2025-11-02T10:00:00.000Z",
"game_context": {
"game_id": "test_game",
"genre_ids": ["platformer"],
"platform_ids": ["pc"]
},
"run_outcome": {
"result": "win",
"path": "combat"
},
"stats": {
"time_s": 900,
"deaths": 0,
"retries": 0,
"distance_traveled": 100,
"jumps": 10,
"hint_offers": 0,
"hints_used": 0,
"riddles_attempted": 1,
"riddles_correct": 1,
"combats_initiated": 5,
"combats_won": 5,
"collectibles_found": 3
},
"config_used": {
"mode": "challenge",
"knobs": {
"enemy_count": 3,
"enemy_speed": 1.0,
"puzzle_gate_ratio": 0.5,
"collectible_density": 0.3,
"hint_delay_ms": 10000,
"breadcrumb_brightness": 0.5
},
"layout_seed": "seed_123"
}
}
}
Key Points:
game_contextis optional but recommended. If provided, it enables game/genre/platform-specific personas.- Only the first genre from
genre_idsarray and first platform fromplatform_idsarray are used. - All fields in
statsare required and must be numbers (can be 0). - The system will automatically:
- Fetch existing personas for trait blending
- Create/update personas for: global, game, genre (first only), platform (first only)
- Compute traits from stats using the trait engine
- Store personas in Supermemory with proper metadata and container tags
Response: 200 OK
{
"global": {
"results": [{"id": "doc_id_1", "status": "queued"}],
"failed": 0,
"success": 1
},
"game": {
"results": [{"id": "doc_id_2", "status": "queued"}],
"failed": 0,
"success": 1
},
"genres": [
{
"results": [{"id": "doc_id_3", "status": "queued"}],
"failed": 0,
"success": 1
}
],
"platforms": [
{
"results": [{"id": "doc_id_4", "status": "queued"}],
"failed": 0,
"success": 1
}
]
}
Behavior:
- Creates/updates 4 personas: global, game, genre (first only), platform (first only)
- Fetches existing personas and blends traits (60% previous, 40% new)
- If no existing persona, creates new one with computed traits
Error Responses:
400 Bad Request: MissingserverInputin body502 Bad Gateway: Supermemory API error
GET /sm/personas
Fetches personas with optional filtering.
Request:
GET /sm/personas?player_id=<id>&scope=<scope>&game_id=<id>&genre_id=<id>&platform_id=<id>&limit=<n>
Query Parameters:
player_id(required): Player identifierscope(optional):"global" | "game" | "genre" | "platform" | "any"(default:"any")game_id(optional): Filter by game IDgenre_id(optional): Filter by genre IDplatform_id(optional): Filter by platform IDlimit(optional): Maximum results (default: 10)
Response: 200 OK
{
"total": 4,
"items": [
{
"id": "doc_id_1",
"metadata": {
"type": "persona",
"persona_scope": "global",
"player_id": "player_123",
"updated_at": "2025-11-02T10:00:00.000Z",
"run_index": 3,
"run_result": "win",
"run_path": "combat",
"game_id": "test_game",
"completed_at": "2025-11-02T10:00:00.000Z"
},
"persona": {
"player_id": "player_123",
"traits": {
"aggression": 0.85,
"stealth": 0.3,
"curiosity": 0.6,
"puzzle_affinity": 0.4,
"independence": 0.9,
"resilience": 0.7,
"goal_focus": 0.8
},
"persona_text": "Shows combat-inclined, exploration-oriented; goal focus 80%.",
"top_signals": [
"Solved 5 riddle(s)",
"Started 10 combat(s), won 10",
"Found 12 collectible(s)"
],
"updated_at": "2025-11-02T10:00:00.000Z"
}
}
]
}
Error Responses:
400 Bad Request: Missingplayer_id502 Bad Gateway: Supermemory API error
Note: Results may be empty immediately after save due to Supermemory indexing delays (10-15 seconds). The client should implement retry logic.
GET /sm/doc/:id
Fetches a specific document by ID.
Request:
GET /sm/doc/{document_id}
Response: 200 OK
{
"id": "doc_id_1",
"content": "# Persona Snapshot\n\n**Player:** player_123\n...",
"metadata": {
"type": "persona",
"persona_scope": "global",
"player_id": "player_123",
...
},
"containerTags": ["persona_global_player_123"],
"status": "done",
"createdAt": "2025-11-02T10:00:00.000Z",
"updatedAt": "2025-11-02T10:00:00.000Z"
}
Error Responses:
502 Bad Gateway: Supermemory API error or document not found
Persona Scopes
Global Scope
Applies across all games. Updated with every run.
- Container Tag:
persona_global_{player_id} - Metadata:
persona_scope: "global"
Game Scope
Specific to a single game. Updated only when runs are for that game.
- Container Tag:
persona_game_{game_id}_player_{player_id} - Metadata:
persona_scope: "game",game_id: {game_id}
Genre Scope
Applies across games in the same genre. Only the first genre from genre_ids array is used.
- Container Tag:
persona_genre_{genre_id}_player_{player_id} - Metadata:
persona_scope: "genre",genre_id: {genre_id}
Platform Scope
Applies across games on the same platform. Only the first platform from platform_ids array is used.
- Container Tag:
persona_platform_{platform_id}_player_{player_id} - Metadata:
persona_scope: "platform",platform_id: {platform_id}
Trait Computation
Traits are computed from Stats using weighted formulas, then normalized to 0.0-1.0 range.
Attribute Mapping
Each trait is calculated from specific gameplay attributes:
| Trait | Attributes Used | Description |
|---|---|---|
| Aggression | combats_initiated, combats_won, mashing_intensity |
Measures combat preference. Higher if player starts and wins more combats, or shows high button mashing intensity. |
| Stealth | combats_initiated, deaths |
Measures avoidance preference. Lower if player engages in combat or dies. |
| Curiosity | collectibles_found, distance_traveled |
Measures exploration tendency. Higher if player finds collectibles and travels more. |
| Puzzle Affinity | riddles_correct, riddles_attempted |
Measures puzzle-solving preference. Higher if player solves more riddles correctly. |
| Independence | hints_used, hint_offers |
Measures hint usage aversion. Perfect score (1.0) if hints offered but none used. Lower if player uses hints. |
| Resilience | retries, deaths, time_s |
Measures failure recovery. Higher if player retries after failure. Lower if they complete too quickly (might indicate avoiding challenges). |
| Goal Focus | time_s, retries, mashing_intensity |
Measures completion efficiency. Higher if player completes quickly, without retries, or shows high button mashing intensity (indicating focused effort). |
Note: The jumps attribute is currently not used in any trait calculation.
Mashing Intensity: Button mashing speed (0.0-1.0) affects aggression and goal_focus. High mashing intensity (>.5) indicates aggressive, focused gameplay and adds a bonus to both traits.
Trait Formulas
// Aggression: Combat preference
// Attributes: combats_initiated (weight: 1.0), combats_won (weight: 0.5), mashing_intensity (bonus: up to 0.3)
// Mashing intensity adds up to 0.3 bonus (min(mashing_intensity, 1) * 0.3)
aggression = (combats_initiated * 1.0 + combats_won * 0.5 + mashing_bonus) / 5
// Stealth: Avoidance preference
// Attributes: combats_initiated (penalty: -0.6 if > 0), deaths (penalty: -0.2 if > 0)
stealth = 1 - (combats_initiated > 0 ? 0.6 : 0) - (deaths > 0 ? 0.2 : 0)
// Curiosity: Exploration tendency
// Attributes: collectibles_found (weight: 0.7), distance_traveled (weight: 0.3, normalized by 500)
curiosity = (collectibles_found * 0.7 + min(distance_traveled/500, 1) * 0.3) / 5
// Puzzle Affinity: Puzzle-solving preference
// Attributes: riddles_correct (weight: 1.0), riddles_attempted (partial credit: 0.2 per wrong)
puzzle_affinity = (riddles_correct * 1.0 + (riddles_attempted - riddles_correct) * 0.2) / 3
// Independence: Hint usage aversion
// Attributes: hints_used (penalty: -0.5 per hint), hint_offers (checks if > 0)
// Perfect score (1.0) if hints offered but none used
independence = (hint_offers > 0 && hints_used === 0) ? 1 : max(0, 1 - hints_used * 0.5)
// Resilience: Failure recovery
// Attributes: retries (weight: +0.8), deaths (weight: +0.4), time_s (penalty: -0.2, normalized by 600s)
// Note: Very fast completion (< 600s) reduces resilience (might indicate avoiding challenges)
resilience = (retries * 0.8 + deaths * 0.4 - min(time_s/600, 1) * 0.2) / 3
// Goal Focus: Completion efficiency
// Attributes: time_s (bonus if < 180s, penalty if > 180s), retries (bonus: +0.2 if 0), mashing_intensity (bonus: up to 0.3)
// Mashing intensity adds up to 0.3 bonus (min(mashing_intensity, 1) * 0.3) indicating focused effort
goal_focus = (time_s < 180 ? 1 : max(0, 1 - (time_s-180)/300)) + (retries === 0 ? 0.2 : 0) + mashing_bonus
goal_focus = goal_focus / 1.4
Calculation Examples
Example 1: Aggression
- Player starts 3 combats, wins 2
- Raw: (3 × 1.0 + 2 × 0.5) = 4.0
- Normalized: 4.0 / 5 = 0.80
Example 2: Independence
- Hints offered: 2, Hints used: 1
- Calculation: max(0, 1 - 1 × 0.5) = 0.5
- If hints offered but none used → 1.0 (perfect independence)
Example 3: Goal Focus
- Completion time: 75s, Retries: 0
- Raw: (1.0 + 0.2) = 1.2
- Normalized: 1.2 / 1.4 = 0.86
Trait Blending
When updating an existing persona, traits are blended with previous values:
blended_trait = 0.6 * previous_trait + 0.4 * new_trait
This creates a weighted average that preserves history (60%) while incorporating new behavior (40%). Traits evolve gradually over time.
First Save: If no previous persona exists, the system blends with default traits (all 0.5) instead of using raw values. This ensures consistent behavior from the first game session.
Persona Text Generation
Human-readable description based on trait thresholds:
puzzle_affinity > 0.6→ "puzzle-leaning"curiosity > 0.6→ "exploration-oriented"aggression > 0.5→ "combat-inclined"independence > 0.6→ "rarely uses hints"resilience > 0.6→ "bounces back after failures"- Default → "balanced playstyle"
Includes goal focus percentage: "goal focus {Math.round(goal_focus * 100)}%"
Top Signals
Top 3 gameplay signals extracted from stats:
"Solved {riddles_correct} riddle(s)"(if riddles_correct > 0)"Started {combats_initiated} combat(s), won {combats_won}"(if combats_initiated > 0)"Found {collectibles_found} collectible(s)"(if collectibles_found > 0)"Used {hints_used} hint(s)"(if hints_used > 0)"Retried {retries} time(s)"(if retries > 0)
Setup & Configuration
Environment Variables
Create a .env file:
# MongoDB (currently optional, not used for personas)
MONGO_URI=mongodb://localhost:27017
# Supermemory API
SUPERMEMORY_BASE_URL=https://api.supermemory.ai
SUPERMEMORY_API_KEY=your_api_key_here
# Game Configuration
GAME_ID=default_game_id # Optional, can be overridden in requests
# API Keys (comma-separated list for authenticating requests)
# Generate a key: npm run generate:api-key
API_KEYS=chk_your_api_key_here,chk_another_key_here
API Key Authentication
All API endpoints (except /health) require API key authentication.
Generate an API Key:
npm run generate:api-key
This will output a secure API key. Add it to your .env file:
API_KEYS=chk_generated_key_here
Multiple API Keys: You can add multiple keys (comma-separated) for different clients:
API_KEYS=chk_key1,chk_key2,chk_key3
Using API Keys in Requests: Provide the API key in one of these ways:
X-API-Keyheader (preferred)Authorization: Bearer <key>header
Example:
curl -X GET http://localhost:7769/sm/personas?player_id=user1 \
-H "X-API-Key: chk_your_api_key_here"
Or:
curl -X GET http://localhost:7769/sm/personas?player_id=user1 \
-H "Authorization: Bearer chk_your_api_key_here"
Installation
npm install
Running the Server
# Development (with hot reload)
npm run dev
# Production
npm run build
npm start
Server runs on http://localhost:7769 by default.
Dependencies
- express: Web server framework
- tsx: TypeScript execution
- zod: Schema validation
- dotenv: Environment variable management
- supermemory: Supermemory SDK (optional, currently using direct API calls)
Usage Examples
Example 1: Save Run Data
curl -X POST http://localhost:7769/sm/save \
-H "Content-Type: application/json" \
-H "X-API-Key: chk_your_api_key_here" \
-d '{
"serverInput": {
"schema_version": "1.0",
"player_id": "player_123",
"session_id": "sess_abc",
"run_index": 1,
"completed_at": "2025-11-02T10:00:00.000Z",
"game_context": {
"game_id": "skyline_runner",
"genre_ids": ["platformer"],
"platform_ids": ["pc"]
},
"run_outcome": {
"result": "win",
"path": "combat"
},
"stats": {
"time_s": 900,
"deaths": 0,
"retries": 0,
"distance_traveled": 500,
"jumps": 20,
"hint_offers": 0,
"hints_used": 0,
"riddles_attempted": 2,
"riddles_correct": 2,
"combats_initiated": 5,
"combats_won": 5,
"collectibles_found": 3
},
"config_used": {
"mode": "challenge",
"knobs": {
"enemy_count": 3,
"enemy_speed": 1.0,
"puzzle_gate_ratio": 0.5,
"collectible_density": 0.3,
"hint_delay_ms": 10000,
"breadcrumb_brightness": 0.5
},
"layout_seed": "seed_123"
}
}
}'
Example 2: Fetch All Personas
curl -X GET "http://localhost:7769/sm/personas?player_id=player_123" \
-H "X-API-Key: chk_your_api_key_here"
Example 2b: Fetch with Filters
curl -X GET "http://localhost:7769/sm/personas?player_id=player_123&limit=10" \
-H "X-API-Key: chk_your_api_key_here"
Example 3: Fetch Global Persona Only
curl -X GET "http://localhost:7769/sm/personas?player_id=player_123&scope=global&limit=1" \
-H "X-API-Key: chk_your_api_key_here"
Example 4: Fetch Platform Persona
curl -X GET "http://localhost:7769/sm/personas?player_id=player_123&scope=platform&platform_id=pc&limit=1" \
-H "X-API-Key: chk_your_api_key_here"
Example 5: Fetch Game Persona
curl -X GET "http://localhost:7769/sm/personas?player_id=player_123&scope=game&game_id=skyline_runner&limit=1" \
-H "X-API-Key: chk_your_api_key_here"
Example 6: Fetch Document by ID
curl -X GET "http://localhost:7769/sm/doc/doc_id_here" \
-H "X-API-Key: chk_your_api_key_here"
Testing
Using the Test Script
The supermemory.test.ts script provides CLI commands for testing.
Save data:
npm run test:supermemory -- save --player user_test --game test_game --genres platformer --platforms pc
Fetch all:
npm run test:supermemory fetch user_test 10
Fetch filtered:
npm run test:supermemory -- fetch --player user_test --scope platform --platform pc --limit 5
Fetch by document ID:
npm run test:supermemory doc <document_id>
Full workflow test:
npm run test:supermemory test-workflow user_test
See TEST_COMMANDS.txt for comprehensive examples.
Important Notes
Indexing Delays: Supermemory may take 10-15 seconds to index new documents. The fetch command includes automatic retry logic.
Single Genre/Platform: Only the first genre and first platform from arrays are used when creating personas.
Trait Blending: Traits are automatically blended (60% previous, 40% new) when updating existing personas.
npm Script Arguments: When using named arguments with npm scripts, use
--separator:npm run test:supermemory -- fetch --player <id> --scope <scope>
Supermemory API Integration
Document Structure
Personas are stored as markdown documents in Supermemory with the following structure:
# Persona Snapshot
**Player:** {player_id}
**Game:** {game_id} (or **Scope:** Global)
**Updated At:** {iso_timestamp}
## Traits
- Aggression: {0.0-1.0}
- Stealth: {0.0-1.0}
- Curiosity: {0.0-1.0}
- Puzzle Affinity: {0.0-1.0}
- Independence: {0.0-1.0}
- Resilience: {0.0-1.0}
- Goal Focus: {0.0-1.0}
## Persona Text
{human_readable_description}
## Top Signals
- {signal_1}
- {signal_2}
- {signal_3}
## Full Persona JSON
```json
{
"player_id": "...",
"traits": {...},
"persona_text": "...",
"top_signals": [...],
"updated_at": "..."
}
### Metadata
Each document includes metadata:
```json
{
"type": "persona",
"persona_scope": "global" | "game" | "genre" | "platform",
"player_id": "...",
"updated_at": "ISO_timestamp",
"run_index": number,
"run_result": "win" | "loss",
"run_path": "combat" | "puzzle" | "exploration",
"game_id": "...", // optional
"genre_id": "...", // optional
"platform_id": "...", // optional
"completed_at": "ISO_timestamp",
"build_version": "...", // optional
"game_title": "..." // optional
}
Container Tags
Documents are organized using container tags:
- Global:
persona_global_{player_id} - Game:
persona_game_{game_id}_player_{player_id} - Genre:
persona_genre_{genre_id}_player_{player_id} - Platform:
persona_platform_{platform_id}_player_{player_id}
Error Handling
Common Errors
400 Bad Request
- Missing required fields
- Invalid data types
502 Bad Gateway
- Supermemory API errors
- Network issues
- Document not found (for GET /sm/doc/:id)
Indexing Delays
- New documents may not appear immediately
- Implement retry logic with exponential backoff
- Wait 10-15 seconds after save before fetch
Retry Logic
The fetch endpoint includes automatic retry:
- 3 retries with increasing delays (3s, 5s, 7s)
- Total possible wait: ~15 seconds
- Only retries on 0 results or server errors (404, 500)
License
ISC
Log in or sign up for Devpost to join the conversation.