Newer
Older
navi-1 / docs / store.md

Session KV Store

PostgreSQL-backed key-value storage for session-scoped data that must survive server restarts.

Purpose

Provides a simple API for tools to persist per-session state without inventing ad-hoc storage mechanisms.

Current consumers:

  • scratchpad — working notes (scope='scratchpad')

Todo plans used to live here (scope='todo') and now have their own table — see todo-store.md. The rows were migrated on first start and removed from session_store.

Future consumers: recall context, wizard state, user preferences per session.

Schema

CREATE TABLE session_store (
    id         SERIAL PRIMARY KEY,
    user_id    TEXT NOT NULL DEFAULT '',
    session_id TEXT NOT NULL,
    scope      TEXT NOT NULL,
    key        TEXT NOT NULL,
    value      TEXT NOT NULL DEFAULT '',
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (user_id, session_id, scope, key)
);

Note on user_id: NULL is normalized to an empty string before every operation. This prevents duplicate rows for anonymous sessions because PostgreSQL treats NULL as distinct in unique constraints.

API (navi.store.KvStore)

from navi.store import KvStore

store = KvStore("postgresql://...")

await store.get(user_id, session_id, scope, key)       # str | None
await store.set(user_id, session_id, scope, key, value)  # None
await store.get_all(user_id, session_id, scope)        # dict[str, str]
await store.delete(user_id, session_id, scope, key)      # None
await store.clear_scope(user_id, session_id, scope)      # None
  • user_id — nullable for legacy single-user mode.
  • session_id — the chat session UUID.
  • scope — namespace: scratchpad, or any custom string.
  • key — arbitrary string identifier within the scope.

Wiring

KvStore is created as a singleton in navi/core/container.py (get_kv_store() exposes it to routes) and injected into build_default_registries(), which passes it to ScratchpadTool on startup. TodoTool gets the dedicated PgTodoStore from the same container.