Append-Only JSONL Pattern

OpenClaw uses append-only JSONL files across multiple subsystems for crash-safe, git-friendly, and streamable persistence.

Use Cases

Session Transcripts

  • Location: src/config/sessions/store.ts
  • Sessions store conversation transcripts as append-only .jsonl files
  • Each line contains a complete JSON object representing a single message or event
  • Session metadata is stored separately in sessions.json

Cron Run Logs

  • Location: src/cron/run-log.ts
  • Cron job execution logs use .jsonl format
  • Auto-pruning at 2MB or 2000 lines (whichever comes first)
  • Constants: DEFAULT_CRON_RUN_LOG_MAX_BYTES = 2_000_000, DEFAULT_CRON_RUN_LOG_KEEP_LINES = 2_000
  • Pruning function: appendCronRunLog() calls pruneIfNeeded() after each append

Write Serialization

OpenClaw uses a Map-based queued write system to ensure serialization:

// From src/cron/run-log.ts
const writesByPath = new Map<string, Promise<void>>();

export async function appendCronRunLog(filePath: string, entry: CronRunLogEntry) {
  const prev = writesByPath.get(filePath) ?? Promise.resolve();
  const next = prev
    .catch(() => undefined)
    .then(async () => {
      await fs.mkdir(path.dirname(filePath), { recursive: true });
      await fs.appendFile(filePath, `${JSON.stringify(entry)}\n`, "utf-8");
      await pruneIfNeeded(filePath, { maxBytes, keepLines });
    });
  writesByPath.set(filePath, next);
  await next;
}

This pattern ensures:

  • Serialized writes: Only one write operation per file at a time
  • No lost writes: Each write waits for the previous one to complete
  • Automatic cleanup: Map entries are removed after completion

Key Benefits

Crash-Safe

  • Partial writes don't corrupt existing data
  • Each line is a complete, valid JSON object
  • If a write fails mid-line, only that line is lost
  • Previous lines remain intact and readable

Git-Friendly

  • Line-based format produces clean diffs
  • Each new event adds exactly one line
  • No bulk rewrites that make git history noisy
  • Merge conflicts are line-based and easier to resolve

Streamable

  • Can read line-by-line without loading entire file
  • Supports large files efficiently
  • Real-time monitoring via tail/follow
  • Easy to filter/search with standard Unix tools

Write Flow Diagram

Cross-References