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
| Provider | Best for |
|---|---|
| SQLite | Local dev, single-server deploys, serverless edge (with Turso) |
| Postgres | Production, high-concurrency, multi-tenant |
| MySQL | Existing 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/pgimport { 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 mysql2import { 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:
| Table | Purpose |
|---|---|
kavach_users | Human user identities, synced from your auth provider |
kavach_tenants | Multi-tenant isolation |
kavach_agents | AI agent identities (the core entity) |
kavach_permissions | Per-agent resource+action permissions with constraints |
kavach_delegation_chains | Agent-to-agent delegation records |
kavach_audit_logs | Immutable log of every agent action |
kavach_rate_limits | Per-agent call-rate counters |
kavach_mcp_servers | Registered MCP servers |
kavach_sessions | KavachOS-managed human user sessions |
kavach_oauth_clients | OAuth 2.1 client registrations (RFC 7591) |
kavach_oauth_access_tokens | Issued access and refresh tokens |
kavach_oauth_authorization_codes | Short-lived PKCE authorization codes |
kavach_agent_cards | A2A capability discovery cards |
kavach_approval_requests | CIBA async approval flow records |
kavach_trust_scores | Graduated autonomy trust scores per agent |
kavach_budget_policies | Token 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
| Provider | Required package |
|---|---|
| SQLite | better-sqlite3 (bundled with core) |
| Postgres | pg |
| MySQL | mysql2 |
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 pgTesting 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.