Skip to main content

Session storage

WednesdayAI persists conversation history in a session store. Plugins can read session data using the plugin SDK’s session access helpers. The storage backend (file, SQLite, Postgres) and optional Redis cache are configured by the administrator — plugins do not register or replace the backend.

Reading sessions from a plugin

The plugin SDK exports loadSessionStore to read the current agent’s session store:
import { loadSessionStore } from "openclaw/plugin-sdk";
import type { OpenClawPluginApi, SessionEntry } from "openclaw/plugin-sdk";
import { Type } from "@sinclair/typebox";

export default function register(api: OpenClawPluginApi): void {
  api.registerTool({
    name: "session_summary",
    description: "Summarise recent conversation history",
    parameters: Type.Object({}),
    async execute(_id, _params) {
      const store = await loadSessionStore();
      const sessions = Object.values(store);
      return {
        content: [{
          type: "text",
          text: `${sessions.length} session(s) in store`,
        }],
      };
    },
  });
}
loadSessionStore() returns Promise<Record<string, SessionEntry>>. Each key is a session key (e.g. agent:<id>:telegram:dm:<userId>); each value is a SessionEntry.

SessionEntry shape

SessionEntry is exported from openclaw/plugin-sdk. Key fields:
FieldTypeDescription
sessionKeystringUnique session identifier
agentIdstringThe agent this session belongs to
createdAtnumberUnix timestamp (ms) of session creation
updatedAtnumberUnix timestamp (ms) of last update
messagesarrayStored conversation entries

Guarding against storage errors

Use StorageReadAccessError to handle permission-denied cases cleanly:
import { loadSessionStore, StorageReadAccessError } from "openclaw/plugin-sdk";

try {
  const store = await loadSessionStore();
  // use store
} catch (err) {
  if (err instanceof StorageReadAccessError) {
    // Plugin does not have read access to this store
    return { content: [{ type: "text", text: "Session data not accessible." }] };
  }
  throw err;
}

Querying conversation history

getConversationReadQuery builds a query object for filtered reads:
import { getConversationReadQuery } from "openclaw/plugin-sdk";

const query = getConversationReadQuery({
  sessionKey: ctx.sessionKey,
  limit: 20,
});

Session storage backends (admin config)

Backend selection and caching are administrator configuration, not plugin configuration. From openclaw.json:
{
  sessions: {
    storage: {
      backend: "sqlite",      // "fs-jsonl" (default) | "sqlite" | "postgres"
      cache: "redis",         // "none" (default) | "redis"
      sqlitePath: "~/.openclaw/sessions.sqlite",
      redisUrl: "redis://localhost:6379",
      redis: {
        keyPrefix: "oc:",
        ttlSeconds: 86400,
      },
    },
  },
}
FieldDefaultDescription
backend"fs-jsonl"Storage engine: per-session JSONL files, SQLite, or PostgreSQL
cache"none"In-memory / Redis write-through cache layer
databaseUrlPostgreSQL connection string (required when backend: "postgres")
sqlitePath~/.openclaw/sessions.sqliteSQLite file path
redisUrlredis://localhost:6379Redis connection URL
redis.keyPrefix"oc:"Namespace prefix for Redis keys
redis.ttlSeconds86400TTL for Redis-cached session entries
fallbackToJsonlOnErrortrueFall back to fs-jsonl if the backend fails
Restart the gateway after changing storage config: systemctl --user restart openclaw-gateway.
Migrating between backends does not automatically copy existing session data. Test on a non-production instance before switching in production.