Local Session Persistence
How .claude/memory/ JSONL files provide offline-first durability, queue management, and session backup for OrchestKit memory.
Local memory is Tier 2 -- the durability layer that ensures no decision is lost even if graph operations fail or sessions end unexpectedly. It uses JSONL (JSON Lines) files in the .claude/memory/ directory, which is auto-created on first write.
The .claude/memory/ Directory
.claude/memory/
decisions.jsonl # All captured decisions and preferences
graph-queue.jsonl # Pending graph operations (entities + relations)
open-problems.jsonl # Tracked open problems and blockers
completed-flows.jsonl # Completed workflow records
tool-preferences.json # Learned tool usage preferencesEach file serves a distinct purpose. The JSONL files form a write-ahead queue system: decisions land in the local files first, then are processed asynchronously into graph storage.
decisions.jsonl -- The Decision Log
This is the primary local backup. Every decision captured by the memory writer is appended here as a single JSON line, regardless of whether graph or cloud storage succeeds.
Record Structure
{
"id": "decision-m1abc-x7y2z3",
"type": "decision",
"content": {
"what": "Use cursor-based pagination for all list endpoints",
"why": "Better performance for large datasets, stable under inserts/deletes",
"alternatives": ["offset-based pagination"],
"constraints": ["Must support 2M+ rows"],
"tradeoffs": ["Slightly more complex client implementation"]
},
"entities": ["cursor-pagination", "PostgreSQL"],
"relations": [],
"identity": {
"user_id": "user@example.com",
"anonymous_id": "anon-abc123",
"machine_id": "machine-xyz"
},
"metadata": {
"session_id": "sess-123",
"timestamp": "2026-02-06T10:30:00.000Z",
"confidence": 0.85,
"source": "user_prompt",
"project": "orchestkit",
"category": "pagination",
"importance": "high",
"is_generalizable": true,
"sharing_scope": "global"
}
}How It Gets Written
The storeDecision function in memory-writer.ts always writes to decisions.jsonl as its first action (step 1 of 4). This ensures the decision is durably stored before any network or MCP operations attempt to process it.
How It Gets Read
The memory-context-loader hook reads this file once per session on the first prompt. It:
- Reads the last 10 lines (most recent decisions)
- Parses each JSON line, skipping any malformed entries
- Reverses the order so the most recent decision appears first
- Formats decisions as markdown with type labels (
[Decision],[Preference]), rationale, and entity tags - Truncates output at 3000 characters to stay within the context budget
Example injected context:
## Recent Project Decisions
- **[Decision]** Use cursor-based pagination for all list endpoints
_(because: Better performance for large datasets)_
`cursor-pagination`, `PostgreSQL`
- **[Preference]** Prefer TypeScript over JavaScript for new modules
- **[Decision]** JWT tokens for auth, not session cookies
_(because: Stateless auth for microservices)_
`JWT`, `session-cookies`
_For deeper graph traversal: use mcp__memory__search_nodes or mcp__memory__open_nodes_graph-queue.jsonl -- Pending Graph Operations
When the memory writer captures a decision, it builds graph operations (entity creation, relation creation, observation additions) and appends them to this queue. Each line represents a single operation to be processed.
Record Structure
{
"type": "create_entities",
"payload": {
"entities": [
{
"name": "decision-m1abc-x7y2z3",
"entityType": "Decision",
"observations": [
"What: Use cursor-based pagination for all list endpoints",
"Rationale: Better performance for large datasets",
"Alternatives considered: offset-based pagination",
"Category: pagination",
"Confidence: 85%",
"Source: user_prompt",
"Project: orchestkit",
"Timestamp: 2026-02-06T10:30:00.000Z"
]
},
{
"name": "cursor-pagination",
"entityType": "Pattern",
"observations": [
"Mentioned in decision: Use cursor-based pagination for all list endpoints"
]
}
]
},
"timestamp": "2026-02-06T10:30:00.001Z"
}A single decision typically generates two queued operations: one create_entities and one create_relations. The buildGraphOperations function in memory-writer.ts produces these operations, including:
- A main entity for the decision itself
- Entities for each mentioned technology/pattern
CHOSEorPREFERSrelations from the decision to its entitiesCHOSE_OVERrelations for rejected alternativesCONSTRAINTrelations for limiting factorsTRADEOFFrelations for accepted costsRELATES_TOcross-links between co-occurring entities
Processing
Graph queue operations are designed to be processed when the MCP memory server is available. The hook system reads pending operations and issues the corresponding mcp__memory__create_entities and mcp__memory__create_relations calls.
open-problems.jsonl -- Problem Tracking
This file tracks unresolved problems and blockers encountered during sessions. Entries include the problem description, related entities, and status.
completed-flows.jsonl -- Workflow Records
Records of completed multi-step workflows, providing an audit trail of complex operations.
tool-preferences.json -- Learned Preferences
Stores learned tool usage preferences (e.g., preferred editor, testing framework) as a JSON object rather than JSONL.
Offline-First Design
The local persistence layer is designed around an offline-first principle:
-
Writes always succeed locally. The
appendToJsonlfunction creates the directory if it does not exist and appends the record. Even if graph MCP is unreachable, the decision is safely stored. -
Queues are processed when services become available. Graph operations queue in
graph-queue.jsonluntil the MCP server processes them. -
No data is lost on unexpected shutdown. Since each write is an atomic file append (not a rewrite), partial writes at most lose one line. The JSONL parser in the context loader silently skips malformed lines.
Health Monitoring
The memory-health.ts library provides detailed health analysis for each JSONL file:
interface FileHealth {
exists: boolean; // File found on disk
lineCount: number; // Total JSONL lines
corruptLines: number; // Lines that fail JSON.parse
sizeBytes: number; // File size
lastModified: string; // Last modification timestamp
}The health check flags:
| Condition | Status | Meaning |
|---|---|---|
.claude/memory/ does not exist | unavailable | No memory directory; nothing has been stored yet |
decisions.jsonl has corrupt lines | degraded | Some entries were partially written; parseable entries still work |
graph-queue.jsonl has 50+ entries | degraded | Queue is backing up; graph operations may not be processing |
Run /ork:memory status to see the full health report.
Synchronization Between Local and Graph
The local layer and graph layer stay in sync through the write-ahead queue pattern:
User decision
|
v
memory-writer.ts
|
+---> decisions.jsonl (always, immediate)
+---> graph-queue.jsonl (always, queued operations)
|
v
Hook processing
|
+---> graph-queue processed --> mcp__memory__create_entitiesThe local files serve as the durable source of truth. If the graph is ever rebuilt or cleared, the decisions in decisions.jsonl can be replayed to reconstruct it.
File Size Management
decisions.jsonlgrows unbounded but is read from the tail (last N lines), so size does not affect read performancegraph-queue.jsonlis cleared as operations are processed
If files become very large, the health check reports degraded status at the 50-entry threshold for queue files, signaling that processing may need attention.
Next Steps
- Graph Memory -- the primary tier that local queues feed into
- Architecture Overview -- the full 3-tier system
Graph Memory (Primary)
The zero-config knowledge graph that stores entities and typed relations as OrchestKit's primary memory tier.
Auto-Sync to MEMORY.md
How OrchestKit auto-promotes high-confidence decisions to Claude Code's native MEMORY.md, ensuring they survive plugin removal.
Last updated on