Skip to main content

Persistence

Storage Layers

The Oasis uses multiple local storage layers:

StorageLocationPurpose
SQLiteprisma/data/oasis.dbMissions, profiles, memories, worlds, snapshots, token burn, app config
Asset Registrydata/conjured-registry.jsonConjured asset metadata
Scene Librarydata/scene-library.jsonSaved crafted scenes that outlive a single world
Hermes Pairingdata/hermes-config.local.jsonStored Hermes API pairing
Hermes Tunneldata/hermes-tunnel.local.jsonSaved SSH tunnel command
GLB Filespublic/conjured/Runtime-generated 3D models
Generated Imagespublic/generated-images/Text-to-image outputs
Legacy Leftoversdata/worlds/*.json, data/oasis.dbOlder artifacts not read by the current world API

All storage is local. There is no required cloud database.

Prisma resolves DATABASE_URL="file:./data/oasis.db" relative to prisma/schema.prisma, so the live SQLite file is prisma/data/oasis.db, not repo-root data/oasis.db.

World Persistence

WorldState Schema

interface WorldState {
version: 1
terrain: TerrainParams | null
groundPresetId?: string
groundTiles?: Record<string, string>
craftedScenes: CraftedScene[]
conjuredAssetIds: string[]
catalogPlacements?: CatalogPlacement[]
transforms: Record<string, Transform>
behaviors?: Record<string, ObjectBehavior>
lights?: WorldLight[]
skyBackgroundId?: string
customGroundPresets?: GroundPreset[]
agentWindows?: AgentWindow[]
agentAvatars?: AgentAvatar[]
savedAt: string
}

Save Strategy

Change detected
-> saveWorldState() in Zustand
-> _worldReady guard check
-> nuke protection check
-> 1000ms debounce
-> PUT /api/worlds/[id]
-> prisma.world.update({ data: JSON.stringify(worldState) })
-> snapshotBeforeSave() + objectCount sync

Browser edits and MCP/subagent edits converge on the same storage target: the World.data JSON string in SQLite.

Migration

migrateIfNeeded() is currently a no-op. The old file-based data/worlds/*.json model is legacy, and there is no live file-to-SQLite loader in the current app.

SQLite Schema (Prisma)

Core Tables

Mission - atomic unit of work

  • Status lifecycle: todo -> wip -> done -> archived
  • Priority triangle: urgency x easiness x impact
  • Scoring: valor x priority x time

World - world container

  • Full WorldState stored in the data field
  • Metadata: name, icon, visibility, visit count, object count
  • One row per world

WorldSnapshot - version history

  • Full WorldState snapshot
  • Source: auto or manual
  • Indexed by worldId and timestamp

Profile - local user data

  • Display name, bio, avatar URLs
  • XP, level, aura

Memory - structured knowledge

  • Categories: preference, habit, goal, fact, pattern
  • Key-value pairs

Journal - timestamped reflections

TokenBurn - aggregated token usage tracking

AppConfig - dynamic settings

CuratorLog and CarbonModelEntry - agent and training metadata

API Routes

RouteMethodsPurpose
/api/worldsGET, POSTList, create, import worlds
/api/worlds/[id]GET, PUT, PATCH, DELETELoad, save, rename, delete a world
/api/worlds/[id]/snapshotsGET, PUT, POSTList, create, restore snapshots
/api/worlds/scene-libraryGET, PUT, POSTPersist the crafted-scene library JSON