@drover/storage
StorageAdapter interface + memory + libsql impls.
StorageAdapter
interface StorageAdapter {
readonly id: string;
createRun(row: RunRow): Effect<void, StorageError>;
updateRun(id, patch): Effect<void, StorageError>;
appendEvent(event: EventRow): Effect<void, StorageError>;
saveCheckpoint(cp: CheckpointRow): Effect<void, StorageError>;
loadRun(id): Effect<RunRow | null, StorageError>;
loadLatestCheckpoint(runId): Effect<CheckpointRow | null, StorageError>;
listEvents(runId): Effect<readonly EventRow[], StorageError>;
listRuns(filter?): Effect<readonly RunRow[], StorageError>;
createPendingConfirmation(row): Effect<void, StorageError>;
resolvePendingConfirmation(runId, toolUseId, result, resolvedAt): Effect<void, StorageError>;
close(): Effect<void, StorageError>;
}Row shapes
interface RunRow {
id: string;
parentRunId?: string;
agentId: string;
specHash: string;
status: "running" | "success" | "quota" | "cancelled" | "error" | "paused";
input: unknown;
output?: unknown;
error?: { tag: string; message: string };
startedAt: number;
endedAt?: number;
tokensIn: number;
tokensOut: number;
costUsd: number;
meta?: Record<string, unknown>;
}
interface EventRow {
runId: string;
seq: number; // monotonic per-run
ts: number;
kind: HarnessEvent["kind"];
payload: HarnessEvent;
}
interface CheckpointRow {
runId: string;
seq: number;
messages: unknown; // pi-agent-core AgentMessage[]
usage: Usage;
toolCalls: readonly string[];
retriesUsed: number;
ts: number;
}createMemoryStorage
function createMemoryStorage(): StorageAdapter;Map-backed, single-process. For tests + short-lived processes.
createLibsqlStorage
function createLibsqlStorage(opts: {
url: string; // ":memory:" | "file:..." | "libsql://..."
authToken?: string;
}): Promise<StorageAdapter>;Migrations run idempotently on the first read/write. Tables:
runs, run_events, run_checkpoints, pending_confirmations,
plus a drover_migrations bookkeeping table.
RunListFilter
interface RunListFilter {
status?: readonly RunRow["status"][];
agentId?: string;
parentRunId?: string | null; // null = no parent (root runs only)
startedAfter?: number;
limit?: number;
offset?: number;
}Concurrency contract
appendEventis per-run monotonic byseq. Implementations must preserve order within a run.- Concurrent runs are isolated by
runId. loadLatestCheckpoint(runId)returns the highestseqfor that run.createPendingConfirmation/resolvePendingConfirmationare forward-compat for confirm-gate-as-control-plane. v0 implementations can no-op them.