skills/runtime/references/agent-runners-sqlite.md
SqliteAgentRunner — file-backed agent runner in @copilotkit/sqlite-runner. Uses better-sqlite3 as a required peer dep.
pnpm add @copilotkit/sqlite-runner better-sqlite3
If better-sqlite3 is missing, the import of @copilotkit/sqlite-runner itself fails
at module load (Cannot find module 'better-sqlite3'). The runner's constructor has a
friendlier multi-line install hint as a fallback, but you will see the bare resolution
error first — install the peer before the runner import resolves.
import { CopilotRuntime } from "@copilotkit/runtime/v2";
import { SqliteAgentRunner } from "@copilotkit/sqlite-runner";
const runtime = new CopilotRuntime({
agents: {
/* ... */
} as any,
runner: new SqliteAgentRunner({
dbPath: "./data/threads.db", // REQUIRED — default is ":memory:"
}),
});
dbPath: ":memory:" is the default if omitted — that reverts to an in-memory store and
loses data at restart. Always set a file path in production.
Three tables are created on first use (packages/sqlite-runner/src/sqlite-runner.ts:75-109):
CREATE TABLE IF NOT EXISTS agent_runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
thread_id TEXT NOT NULL,
run_id TEXT NOT NULL UNIQUE,
parent_run_id TEXT,
events TEXT NOT NULL, -- JSON-encoded BaseEvent[]
input TEXT NOT NULL, -- JSON-encoded RunAgentInput
created_at INTEGER NOT NULL,
version INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS run_state (
thread_id TEXT PRIMARY KEY,
is_running INTEGER DEFAULT 0,
current_run_id TEXT,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS schema_version (
version INTEGER PRIMARY KEY,
applied_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_thread_id ON agent_runs(thread_id);
CREATE INDEX IF NOT EXISTS idx_parent_run_id ON agent_runs(parent_run_id);
agent_runs is append-only — one row per completed run, full event log in the events
column. run_state gates concurrent runs (the "Thread already running" check).
schema_version tracks applied migrations so future releases can upgrade existing
databases in place.
There is no automatic retention. If you need bounded history, add a periodic purge:
import Database from "better-sqlite3";
const db = new Database("./data/threads.db");
setInterval(
() => {
const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000; // 30 days
db.prepare("DELETE FROM agent_runs WHERE created_at < ?").run(cutoff);
},
60 * 60 * 1000,
);
Source: packages/sqlite-runner/src/sqlite-runner.ts.