Skip to main content

KV Store

Fast namespaced key-value storage for persisting small values across agent runs. The KV Store is SQLite-backed with WAL mode for concurrent access, instance-based (each instance is a logical container with its own namespaces), and delivers real-time updates via WebSocket.

KV Store


Storage Layout

ItemLocation
Database{projectPath}/.codebolt/db/project.db (shared with VectorDB and EventLog)
Instance metadata{projectPath}/.codebolt/kvstore/instances/{instanceId}.json

Table Schema — kv_store

ColumnTypeDescription
instance_idTEXTUUID of the owning instance
namespaceTEXTLogical namespace within the instance
keyTEXTRecord key
valueTEXTJSON-stringified value
updated_atINTEGEREpoch milliseconds of last write

Primary key: (instance_id, namespace, key) — conflicts trigger an upsert.

SQLite PRAGMAs

The database is configured with:

  • WAL mode for concurrent reads and writes
  • Appropriate synchronous level for durability
  • TEMP_STORE MEMORY to keep temporary tables in RAM

Instance Management

Each instance is a logical container described by the following metadata:

FieldDescription
idUUID
nameHuman-readable name
descriptionOptional description
recordCountLive count of records (computed from DB on read)
namespacesLive list of namespaces (computed from DB on read)
createdAtCreation timestamp
updatedAtLast-modified timestamp

Metadata is stored as individual JSON files and sorted by createdAt DESC when listed.

ensureInstance(name)

Uses a get-or-create pattern: looks up an existing instance by name, and creates a new one if no match is found. This is the mechanism behind the autoCreateInstance flag on the set endpoint.


Data Operations

set(instanceId, namespace, key, value)

Upserts a record into kv_store. The value is JSON.stringify-ed before storage. Emits the kvStore:record-set WebSocket event on success.

get(instanceId, namespace, key)

Retrieves a record and JSON.parses its value. Returns null if the key does not exist.

delete(instanceId, namespace, key)

Deletes a single record. Emits kvStore:record-deleted. Returns a boolean indicating whether a row was removed.

deleteNamespace(instanceId, namespace)

Deletes all records in the given namespace. Emits kvStore:namespace-cleared. Returns the count of deleted records.

getNamespaces(instanceId)

Returns a sorted array of distinct namespace values for the instance.

getRecordCount(instanceId, namespace?)

Counts records belonging to an instance, optionally filtered to a single namespace.


Query DSL

The KVQueryDSL allows structured queries against the store:

interface KVQueryDSL {
instance: string; // instance ID
namespace: string; // namespace to query
where?: KVFilter[]; // filter conditions
order?: KVQueryOrder; // sorting
limit?: number; // max results
}

Filter Operators (KVFilterOperator)

OperatorDescription
=Exact match
!=Not equal
starts_withKey prefix match
containsSubstring match
inValue is in a provided array
existsKey exists (no value argument needed)

Allowed filter fields: key, updated_at

Query Compilation

KVQueryDSL is compiled to parameterized SQL via kvStoreQueryCompiler. The compiler translates the filter array and ordering into WHERE clauses with bound parameters, preventing SQL injection.

Constraints

ConstraintValue
MAX_FILTERS5
MAX_QUERY_LIMIT500
DEFAULT_QUERY_LIMIT100

Database Migrations

Two migrations are applied in order:

  1. CreateKVStoreTable — creates the kv_store table with columns instance_id, namespace, key, value, and updated_at.
  2. CreateKVMigrationsTable — creates the migration-tracking table used to record which migrations have run.

WebSocket Events

All mutations emit events so that connected clients (e.g., the UI panel) can update in real time.

EventPayload
kvStore:instance-createdInstance metadata
kvStore:instance-updatedInstance metadata
kvStore:instance-deletedInstance ID
kvStore:record-set{ instanceId, namespace, key, value }
kvStore:record-deleted{ instanceId, namespace, key }
kvStore:namespace-cleared{ instanceId, namespace }

REST API

Instance Endpoints

MethodPathDescription
POST/kvstore/instancesCreate a new instance
GET/kvstore/instancesList all instances
GET/kvstore/instances/:idGet instance with live stats (recordCount, namespaces)
PUT/kvstore/instances/:idUpdate instance name or description
DELETE/kvstore/instances/:idDelete instance and all its records

Record Endpoints

MethodPathDescription
POST/kvstore/kvSet a value. Body: { instanceId, namespace, key, value, autoCreateInstance }
GET/kvstore/kv/:instanceId/:namespace/:keyGet a value
DELETE/kvstore/kv/:instanceId/:namespace/:keyDelete a value
DELETE/kvstore/namespace/:instanceId/:namespaceDelete all records in a namespace

Query Endpoint

MethodPathDescription
POST/kvstore/queryExecute a KVQueryDSL query

Note: The autoCreateInstance flag on the set endpoint triggers ensureInstance — if the instance name does not exist yet, it is created automatically before the value is written.


SDK Reference

Create an Instance

const instance = await codebolt.kvstore.createInstance({
name: "my-agent-state",
description: "Persisted state for my agent"
});

Set a Value

await codebolt.kvstore.set(instance.id, "config", "theme", "dark");

Get a Value

const theme = await codebolt.kvstore.get(instance.id, "config", "theme");
// "dark"

Delete a Value

await codebolt.kvstore.delete(instance.id, "config", "theme");

Delete a Namespace

const deletedCount = await codebolt.kvstore.deleteNamespace(instance.id, "config");

Query Records

const results = await codebolt.kvstore.query({
instance: instance.id,
namespace: "user-prefs",
where: [
{ field: "key", operator: "starts_with", value: "ui." }
],
order: { field: "updated_at", direction: "desc" },
limit: 50
});

Get Namespaces

const namespaces = await codebolt.kvstore.getNamespaces(instance.id);
// ["config", "user-prefs"]

Get Record Count

const total = await codebolt.kvstore.getRecordCount(instance.id);
const nsCount = await codebolt.kvstore.getRecordCount(instance.id, "config");