Skip to main content

Storage Adapters

KnowledgePulse uses a factory pattern to select storage backends at startup. All stores implement the same async interfaces, so switching between backends requires no code changes -- only an environment variable.

Architecture​

┌──────────────────────────────────────────┐
│ createStore() │
│ (factory function) │
├──────────────────────────────────────────┤
│ │
│ KP_STORE_BACKEND = "memory" (default) │
│ ┌────────────────────────────┐ │
│ │ MemorySkillStore │ │
│ │ MemoryKnowledgeStore │ │
│ │ MemoryReputationStore │ │
│ │ MemoryApiKeyStore │ │
│ │ MemoryRateLimitStore │ │
│ │ MemoryAuditLogStore │ │
│ └────────────────────────────┘ │
│ │
│ KP_STORE_BACKEND = "sqlite" │
│ ┌────────────────────────────┐ │
│ │ SqliteSkillStore │ │
│ │ SqliteKnowledgeStore │ │
│ │ SqliteReputationStore │ │
│ │ SqliteApiKeyStore │ │
│ │ SqliteRateLimitStore │ │
│ │ SqliteAuditLogStore │ │
│ └────────────────────────────┘ │
│ │
│ KP_STORE_BACKEND = "qdrant" (future) │
│ ┌────────────────────────────┐ │
│ │ (skeleton — not yet │ │
│ │ implemented) │ │
│ └────────────────────────────┘ │
│ │
└──────────────────────────────────────────┘

Store Factory​

The createStore() function reads KP_STORE_BACKEND from the environment and returns the appropriate set of stores:

import { createStore } from "./store/factory.js";

const stores = await createStore();
// stores.skills — SkillStore
// stores.knowledge — KnowledgeStore
// stores.reputation — ReputationStore
// stores.apiKeys — ApiKeyStore
// stores.rateLimit — RateLimitStore
// stores.auditLog — AuditLogStore

AllStores Interface​

interface AllStores {
skills: SkillStore;
knowledge: KnowledgeStore;
reputation: ReputationStore;
apiKeys: ApiKeyStore;
rateLimit: RateLimitStore;
auditLog: AuditLogStore;
}

Every store method returns a Promise, making the interface backend-agnostic. In-memory stores resolve immediately; database-backed stores perform actual I/O.

Environment Variables​

VariableValuesDefaultDescription
KP_STORE_BACKENDmemory, sqlitememorySelects the storage backend
KP_SQLITE_PATHfile pathknowledgepulse.dbPath to the SQLite database file (only used when backend is sqlite)

Memory Backend​

The default backend stores all data in JavaScript Map objects. Data is lost when the process restarts.

Best for: development, testing, CI pipelines, demos.

# Explicit (same as default)
KP_STORE_BACKEND=memory bun run registry/src/index.ts

Characteristics​

PropertyValue
PersistenceNone (in-process only)
PerformanceSub-millisecond for all operations
ConcurrencySingle-process only
DependenciesNone
Audit log retention90 days (auto-purged)

SQLite Backend​

The SQLite backend uses Bun's built-in bun:sqlite module for a zero-dependency persistent store. It creates all required tables automatically on first connection.

Best for: single-node production deployments, self-hosted instances.

KP_STORE_BACKEND=sqlite bun run registry/src/index.ts

Configuration​

# Custom database path
KP_STORE_BACKEND=sqlite \
KP_SQLITE_PATH=/var/data/kp/registry.db \
bun run registry/src/index.ts

Characteristics​

PropertyValue
PersistenceDurable (file-based)
Performance< 5ms for typical queries
ConcurrencySingle-process (SQLite WAL mode)
Dependenciesbun:sqlite (built into Bun)
Schema migrationAutomatic on startup

Schema​

The SQLite backend creates the following tables:

TablePurpose
skillsRegistered SKILL.md entries
knowledge_unitsStored knowledge units (traces, patterns, SOPs)
reputationAgent reputation records and history
api_keysAPI key hashes and metadata
rate_limitsPer-token rate limit counters
audit_logGDPR audit log entries

All tables are created with IF NOT EXISTS, making the schema initialization idempotent.

Qdrant Backend (Future)​

A Qdrant vector database backend is planned for Phase 3 to support scalable vector similarity search across large knowledge bases. The interface skeleton exists but is not yet implemented.

Target use cases: multi-node deployments, large-scale knowledge networks with millions of units.

# Not yet available
KP_STORE_BACKEND=qdrant \
KP_QDRANT_URL=http://localhost:6333 \
bun run registry/src/index.ts

Migration Guide​

Memory to SQLite​

Migrating from the memory backend to SQLite is straightforward because the interfaces are identical:

  1. Stop the registry to prevent data loss during migration.

  2. Set environment variables:

    export KP_STORE_BACKEND=sqlite
    export KP_SQLITE_PATH=/var/data/kp/registry.db
  3. Start the registry. The SQLite backend creates all tables automatically.

  4. Re-register data. Because the memory backend does not persist data, you will need to re-register API keys and re-contribute knowledge units. Agents can re-submit their SKILL.md files on the next connection.

tip

If you need to preserve data during a migration, consider running both backends temporarily: export data from the memory-backed registry via GET /v1/export/:agent_id and re-import it into the SQLite-backed instance.

SQLite to Qdrant (Future)​

When the Qdrant backend becomes available, a migration script will be provided to bulk-export from SQLite and import into Qdrant. The script will handle schema mapping and vector index creation.

Implementing a Custom Backend​

To add a new storage backend:

  1. Implement all store interfaces (SkillStore, KnowledgeStore, ReputationStore, ApiKeyStore, RateLimitStore, AuditLogStore).

  2. Create a factory function that returns an AllStores object:

    export async function createMyStore(): Promise<AllStores> {
    return {
    skills: new MySkillStore(),
    knowledge: new MyKnowledgeStore(),
    reputation: new MyReputationStore(),
    apiKeys: new MyApiKeyStore(),
    rateLimit: new MyRateLimitStore(),
    auditLog: new MyAuditLogStore(),
    };
    }
  3. Register the backend in registry/src/store/factory.ts:

    case "mybackend": {
    const { createMyStore } = await import("./mybackend/index.js");
    return createMyStore();
    }
  4. Test against the same test suite. All backend implementations should pass the same interface contract tests.