Persistence layers for the Medicine Wheel ecosystem — JSONL file-based storage for development/community use, and Redis-backed persistence for production scale.
Version: 0.2.0
Package: medicine-wheel-data-store
Document ID: rispec-data-store-v2
Last Updated: 2026-04-04
Users create persistent relational knowledge graphs that survive process restarts and are visible across all interfaces (Web UI, MCP tools, terminal agents) without requiring external infrastructure for development and community deployments.
What this enables: Any Medicine Wheel application can persist its ontological data — ceremonies, cycles, nodes, edges, beats, structural tension charts — to shared storage that all interfaces read/write simultaneously. Communities can start with zero-infrastructure JSONL files and graduate to Redis when scale demands it.
Structural Tension: Between in-memory-only ephemeral data (fast but invisible across processes) and durable queryable persistence (survives restarts, shareable). The JSONL layer resolves this with file-based persistence; the Redis layer resolves it with network-accessible persistence.
Location: lib/jsonl-store.ts (Web UI) + mcp/src/jsonl-store.ts (MCP server)
Data directory: .mw/store/ (configurable via MW_DATA_DIR)
Both the Next.js Web UI and MCP server processes read/write the same JSONL files on disk. Cross-process synchronization is handled via file mtime checking — if one process writes, the other detects the change and reloads from disk.
.mw/store/
├── nodes.jsonl # Relational nodes (human, land, spirit, ancestor, future, knowledge)
├── edges.jsonl # Relational edges between nodes
├── ceremonies.jsonl # Ceremony logs
├── beats.jsonl # Narrative beats
├── cycles.jsonl # Medicine wheel research cycles
├── charts.jsonl # Structural tension charts
└── mmots.jsonl # Moment of truth reviews
Each file is JSONL format: one JSON record per line. Entity collections (nodes, ceremonies, beats, cycles, charts, mmots) are keyed by the record’s id field with full upsert semantics — writing the same id twice replaces the first record. Edges are keyed by ${from_id}:${to_id} (or the edge’s explicit id) and also use upsert — re-adding the same edge endpoints updates the existing record rather than duplicating it.
Process A (Web UI) Shared Disk Process B (MCP)
│ │ │
├── write ceremony ──────────► │ ceremonies.jsonl │
│ (atomic: tmp+rename) │ mtime updated │
│ │ │
│ │ ◄───────── list_ceremonies│
│ │ (check mtime → reload) │
│ │ returns ceremony ──────┤
| Variable | Default | Purpose |
|---|---|---|
MW_DATA_DIR |
.mw/store/ (project root) |
Override JSONL data directory |
The MCP server resolves the project root automatically (from mcp/ subdirectory → parent .mw/store/).
Each write is a read-modify-write inside a file lock:
O_EXCL create of file.jsonl.lock (atomic on POSIX) and write an ownership token into the lock filefile.jsonl.tmp.<pid>fs.renameSync() to file.jsonl (atomic)This prevents last-writer-wins data loss when the Web UI and MCP server write simultaneously.
.mw/ directory conventions are establishedLocation: src/data-store/
Package: medicine-wheel-data-store
Redis-backed persistence for production deployments. Supports Upstash, Vercel KV, and local Redis.
interface RedisConnectionConfig {
url?: string; // Default: "redis://localhost:6379"
prefix?: string; // Key prefix, default: "mw:"
autoConnect?: boolean; // Default: true
retryAttempts?: number; // Default: 3
retryDelay?: number; // Default: 1000 (ms)
}
createConnection(config?: RedisConnectionConfig): Promise<MWRedisClient>
getConnection(): MWRedisClient
closeConnection(): Promise<void>
isConnected(): boolean
CRUD for RelationalNode, RelationalEdge, and CeremonyLog types from ontology-core.
Nodes: putNode, getNode, deleteNode, listNodes
Edges: putEdge, getEdge, deleteEdge, listEdges
Ceremonies: putCeremony, getCeremony, deleteCeremony, listCeremonies
Bidirectional links between external sessions and Medicine Wheel ceremonies:
linkSessionToCeremony(sessionId: string, ceremonyId: string): Promise<void>
getCeremoniesForSession(sessionId: string): Promise<string[]>
getSessionsForCeremony(ceremonyId: string): Promise<string[]>
Both JSONL and Redis backends expose the same logical operations:
createNode(node: RelationalNode): void
getNode(id: string): RelationalNode | undefined
getAllNodes(limit?: number): RelationalNode[]
getNodesByType(type: string): RelationalNode[]
getNodesByDirection(direction: string): RelationalNode[]
searchNodes(query: string, opts?: { type?; direction?; limit? }): RelationalNode[]
createEdge(edge: RelationalEdge): void
getEdgesForNode(nodeId: string): RelationalEdge[]
getRelatedNodeIds(nodeId: string): string[]
getRelationalWeb(nodeId: string, depth?: number): { nodes; edges }
updateEdgeCeremony(fromId: string, toId: string, ceremonyId: string): void // updates only the directed edge that matches fromId -> toId
logCeremony(ceremony: CeremonyLog): void
getCeremony(id: string): CeremonyLog | undefined
getAllCeremonies(limit?: number): CeremonyLog[]
getCeremoniesByDirection(direction: string): CeremonyLog[]
getCeremoniesByType(type: string): CeremonyLog[]
createBeat(beat: NarrativeBeat): void
getBeat(id: string): NarrativeBeat | undefined
getAllBeats(limit?: number): NarrativeBeat[]
getBeatsByDirection(direction: string): NarrativeBeat[]
createCycle(cycle: MedicineWheelCycle): void
getCycle(id: string): MedicineWheelCycle | undefined
getAllCycles(): { active: MedicineWheelCycle[]; archived: MedicineWheelCycle[] }
archiveCycle(id: string): void
saveChart(chart: StructuralTensionChart): void
getChart(id: string): StructuralTensionChart | undefined
getAllCharts(direction?: string): StructuralTensionChart[]
saveMmot(mmot: MmotReview): void
getMmotsByChart(chartId: string): MmotReview[]
.mw/ Directory ConventionThe .mw/ directory follows the Medicine Wheel workspace convention established across the ecosystem:
.mw/
├── store/ # JSONL data files (this spec)
│ ├── nodes.jsonl
│ ├── edges.jsonl
│ ├── ceremonies.jsonl
│ ├── beats.jsonl
│ ├── cycles.jsonl
│ ├── charts.jsonl
│ └── mmots.jsonl
├── east/ # Vision artifacts (optional)
├── south/ # Planning artifacts (optional)
├── west/ # Implementation artifacts (optional)
├── north/ # Reflection artifacts (optional)
├── ceremonies/ # Ceremony crossing artifacts (optional)
└── README.md # Workspace description
The store/ subdirectory is created automatically by the JSONL persistence engine. The directional subdirectories are optional and follow the .mw/ convention from other ecosystem projects.
fs, path only — zero external dependenciesontology-core typesmedicine-wheel-ontology-core ^0.1.0, redis ^4.6.0RelationalNode, RelationalEdge, CeremonyLog, CeremonyPhase, OcapFlagscat .mw/store/ceremonies.jsonl reveals all ceremonies in plain text.mw/store/ is ignored by default in standard repo setups.mw/store/*.jsonl files or subdirectories.mw/ convention alignment — Follows the ecosystem-wide directional workspace pattern