kavachOS

Database setup

Configure SQLite, Postgres, or MySQL.

KavachOS uses Drizzle ORM under the hood. You pick a provider and pass the connection URL; KavachOS handles the rest.

Choosing a provider

ProviderBest for
SQLiteLocal dev, single-server deploys, serverless edge (with Turso)
PostgresProduction, high-concurrency, multi-tenant
MySQLExisting MySQL infrastructure

Setup

SQLite is the default for development. No peer dependencies beyond better-sqlite3, which ships with @kavachos/core.

import { createKavach } from '@kavachos/core';

const kavach = await createKavach({
  database: {
    provider: 'sqlite',
    url: './kavach.db',
  },
});

For in-memory SQLite (tests and CI), use :memory: as the URL:

const kavach = await createKavach({
  database: {
    provider: 'sqlite',
    url: ':memory:',
  },
});

KavachOS enables WAL mode and foreign keys automatically:

PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;

Install the pg peer dependency:

npm install pg
npm install --save-dev @types/pg
import { createKavach } from '@kavachos/core';

const kavach = await createKavach({
  database: {
    provider: 'postgres',
    url: process.env.DATABASE_URL!,
    // postgresql://user:password@host:5432/dbname
  },
});

KavachOS uses drizzle-orm/node-postgres with a connection pool via pg.Pool. The pg package is loaded with a dynamic import, so it stays an optional peer dep.

Install the mysql2 peer dependency:

npm install mysql2
import { createKavach } from '@kavachos/core';

const kavach = await createKavach({
  database: {
    provider: 'mysql',
    url: process.env.DATABASE_URL!,
    // mysql://user:password@host:3306/dbname
  },
});

KavachOS uses drizzle-orm/mysql2 with a connection pool. mysql2 is loaded via dynamic import and stays an optional peer dep.

Auto-migration

By default, KavachOS calls CREATE TABLE IF NOT EXISTS for all its tables on startup. This means your database is always ready to use without any manual migration step.

To disable this (e.g. when you manage migrations externally with Flyway, Liquibase, or drizzle-kit push), set skipMigrations: true:

database: {
  provider: 'postgres',
  url: process.env.DATABASE_URL!,
  skipMigrations: true,
},

When skipMigrations: true, you are responsible for keeping the schema in sync. KavachOS will fail at runtime if expected tables or columns are missing.

Schema overview

KavachOS creates the following tables in your database:

TablePurpose
kavach_usersHuman user identities, synced from your auth provider
kavach_tenantsMulti-tenant isolation
kavach_agentsAI agent identities (the core entity)
kavach_permissionsPer-agent resource+action permissions with constraints
kavach_delegation_chainsAgent-to-agent delegation records
kavach_audit_logsImmutable log of every agent action
kavach_rate_limitsPer-agent call-rate counters
kavach_mcp_serversRegistered MCP servers
kavach_sessionsKavachOS-managed human user sessions
kavach_oauth_clientsOAuth 2.1 client registrations (RFC 7591)
kavach_oauth_access_tokensIssued access and refresh tokens
kavach_oauth_authorization_codesShort-lived PKCE authorization codes
kavach_agent_cardsA2A capability discovery cards
kavach_approval_requestsCIBA async approval flow records
kavach_trust_scoresGraduated autonomy trust scores per agent
kavach_budget_policiesToken and call budget caps per agent/user/tenant

All table and column names use snake_case. All IDs are text (UUID or CUID2). Timestamps are stored as Unix seconds integers.

Peer dependencies

ProviderRequired package
SQLitebetter-sqlite3 (bundled with core)
Postgrespg
MySQLmysql2

KavachOS uses dynamic imports for Postgres and MySQL drivers so they remain optional. You will get a clear error message at startup if the required package is missing:

KavachOS: provider "postgres" requires the "pg" package.
Install it with: npm install pg

Testing with in-memory SQLite

Use :memory: for fast, isolated tests that need no setup or teardown:

import { createKavach } from '@kavachos/core';
import { describe, beforeEach, it } from 'vitest';

let kavach: Awaited<ReturnType<typeof createKavach>>;

beforeEach(async () => {
  kavach = await createKavach({
    database: { provider: 'sqlite', url: ':memory:' },
    agents: { enabled: true },
  });
});

it('creates an agent', async () => {
  const agent = await kavach.agent.create({
    ownerId: 'user_1',
    name: 'Test Agent',
    type: 'autonomous',
    permissions: [],
  });

  expect(agent.id).toBeDefined();
});

Each createKavach() call with :memory: gets a completely isolated database, so tests never share state.

On this page