Database Schema¶
oAI-Web uses two PostgreSQL databases: - aide — main application database (schema v28) - brain — 2nd Brain vector search database (separate instance, pgvector)
Migrations are applied automatically at startup. The migration system is append-only: new migrations are added to the _MIGRATIONS list in server/database.py and run once in order.
aide database¶
schema_version¶
Tracks which migrations have been applied.
credentials¶
Encrypted key-value store for API keys and system settings.
key TEXT PRIMARY KEY
value_enc TEXT NOT NULL -- AES-256-GCM encrypted, base64-encoded
description TEXT
created_at TEXT NOT NULL
updated_at TEXT NOT NULL
Well-known keys:
| Key | Value |
|---|---|
system:anthropic_api_key |
Anthropic API key |
system:openrouter_api_key |
OpenRouter API key |
system:openai_api_key |
OpenAI API key |
system:default_provider |
"anthropic", "openrouter", or "openai" |
system:session_secret |
Auto-generated HMAC signing secret for session cookies |
system:max_tool_calls |
Runtime limit override |
system:max_autonomous_runs_per_hour |
Runtime limit override |
system:max_concurrent_runs |
Concurrency semaphore value |
system:paused |
"1" when the kill switch is active |
system:audit_retention_days |
Days to retain audit log entries |
system:users_base_folder |
Base path for user file folders |
system:security_* |
Security option flags |
system:trusted_proxy_ips |
Comma-separated trusted proxy IPs |
users¶
Multi-user authentication.
id TEXT PRIMARY KEY -- UUID (stored as TEXT, not UUID type)
username TEXT UNIQUE NOT NULL
password_hash TEXT NOT NULL -- Argon2id
role TEXT NOT NULL -- 'admin' | 'user'
is_active BOOLEAN NOT NULL DEFAULT TRUE
totp_secret TEXT -- Base32 TOTP secret (NULL = MFA disabled)
email TEXT -- Optional, for account recovery
created_at TEXT NOT NULL
updated_at TEXT NOT NULL
user_settings¶
Per-user key-value store (plaintext).
Common keys:
| Key | Value |
|---|---|
personality_soul |
Per-user SOUL.md override |
personality_user |
Per-user USER.md override |
brain_auto_approve |
"1" if brain tool auto-approved |
brain_mcp_key |
Per-user Brain MCP API key |
pushover_user_key |
Per-user Pushover key |
anthropic_api_key |
Per-user Anthropic API key override |
preferred_theme |
UI theme preference |
audit_log¶
Append-only tool call log.
id BIGSERIAL PRIMARY KEY
timestamp TEXT NOT NULL -- ISO8601 UTC
session_id TEXT -- NULL for scheduled tasks
tool_name TEXT NOT NULL
arguments JSONB
result_summary TEXT
confirmed BOOLEAN NOT NULL DEFAULT FALSE
task_id TEXT -- NULL for interactive sessions
user_id TEXT -- NULL for system/scheduled
Indexes: timestamp, session_id, tool_name
conversations¶
Chat history.
id TEXT PRIMARY KEY -- session UUID
started_at TEXT
ended_at TEXT
messages JSONB -- array of {role, content, tool_calls?, ...}
task_id TEXT
user_id TEXT
title TEXT -- derived from first user message
model TEXT -- last model used
Note: messages uses JSONB. asyncpg requires manual codec registration to auto-serialize dicts — see database.py::_init_connection().
Agent tables¶
agents¶
Agent definitions.
id TEXT PRIMARY KEY -- UUID
name TEXT NOT NULL
description TEXT
prompt TEXT NOT NULL
model TEXT NOT NULL
can_create_subagents BOOLEAN DEFAULT FALSE
allowed_tools JSONB -- NULL = all tools; [] also treated as NULL
schedule TEXT -- cron expression; NULL = manual only
enabled BOOLEAN DEFAULT TRUE
max_tool_calls INTEGER -- NULL = use system default
prompt_mode TEXT DEFAULT 'combined' -- 'combined' | 'system_only' | 'agent_only'
parent_agent_id TEXT -- NULL = top-level
created_by TEXT -- 'user' or parent agent ID
owner_user_id TEXT -- NULL = system agent
created_at TEXT NOT NULL
updated_at TEXT NOT NULL
prompt_mode values:
- combined: agent prompt prepended to standard system prompt (SOUL.md + security rules)
- system_only: standard system prompt only; agent prompt sent as the task message
- agent_only: agent prompt replaces the entire system prompt (no SOUL.md, no security rules — use with caution)
agent_runs¶
Execution history.
id TEXT PRIMARY KEY -- UUID
agent_id TEXT NOT NULL REFERENCES agents(id)
started_at TEXT NOT NULL
ended_at TEXT
status TEXT -- 'running' | 'success' | 'error' | 'stopped'
input_tokens INTEGER DEFAULT 0
output_tokens INTEGER DEFAULT 0
cost_usd REAL -- NULL placeholder
result TEXT -- final response text
error TEXT -- error message if status='error'
scheduled_tasks¶
Simple cron tasks (predecessor to agents; still used for lightweight automation).
id TEXT PRIMARY KEY
name TEXT NOT NULL
description TEXT
schedule TEXT
prompt TEXT NOT NULL
allowed_tools TEXT -- JSON array
enabled BOOLEAN DEFAULT TRUE
last_run TEXT
last_status TEXT
created_at TEXT
updated_at TEXT
Security tables¶
email_whitelist¶
web_whitelist¶
Tier 1 trusted domains for the web tool.
Seeded on first migration: duckduckgo.com, wikipedia.org, weather.met.no, api.met.no, yr.no, timeanddate.com
Subdomain matching is implemented in code: wikipedia.org automatically covers en.wikipedia.org.
filesystem_whitelist¶
browser_approved_domains¶
Per-user trusted domains for interactive browser operations (skip confirmation).
id TEXT PRIMARY KEY
owner_user_id TEXT NOT NULL -- TEXT, not UUID type
domain TEXT NOT NULL
note TEXT
created_at TEXT NOT NULL
UNIQUE(owner_user_id, domain)
Integration tables¶
email_accounts¶
Multi-account IMAP/SMTP configuration.
id TEXT PRIMARY KEY
user_id TEXT -- owner user; NULL = global
label TEXT NOT NULL
account_type TEXT NOT NULL -- 'trigger' | 'handling'
imap_host TEXT
imap_port INTEGER DEFAULT 993
imap_username TEXT
imap_password_enc TEXT -- AES-GCM encrypted
smtp_host TEXT
smtp_port INTEGER DEFAULT 465
smtp_username TEXT
smtp_password_enc TEXT
agent_id TEXT -- agent to dispatch on trigger
monitored_folders JSONB -- NULL = all folders
initial_load_done BOOLEAN DEFAULT FALSE
initial_load_limit INTEGER DEFAULT 200
extra_tools JSONB -- additional tools for handling accounts
telegram_chat_id TEXT -- optional: route to Telegram chat
telegram_keyword TEXT -- optional: Telegram /keyword routing
enabled BOOLEAN DEFAULT TRUE
created_at TEXT
updated_at TEXT
email_triggers¶
id TEXT PRIMARY KEY
trigger_word TEXT NOT NULL
agent_id TEXT NOT NULL
account_id TEXT
user_id TEXT
enabled BOOLEAN DEFAULT TRUE
created_at TEXT
telegram_whitelist¶
id TEXT PRIMARY KEY
chat_id TEXT NOT NULL
user_id TEXT -- NULL = global whitelist entry
label TEXT
UNIQUE(chat_id, user_id) NULLS NOT DISTINCT
telegram_triggers¶
id TEXT PRIMARY KEY
trigger_word TEXT NOT NULL
agent_id TEXT NOT NULL
user_id TEXT
enabled BOOLEAN DEFAULT TRUE
created_at TEXT
webhook_endpoints¶
Inbound webhook endpoints that can trigger agents via HTTP POST.
id TEXT PRIMARY KEY
name TEXT NOT NULL
token TEXT NOT NULL UNIQUE -- used in URL: /webhook/{token}
agent_id TEXT
owner_user_id TEXT
allow_get BOOLEAN DEFAULT FALSE
enabled BOOLEAN DEFAULT TRUE
trigger_count INTEGER DEFAULT 0
last_triggered_at TEXT
created_at TEXT
webhook_targets¶
Outbound webhook destinations.
id TEXT PRIMARY KEY
name TEXT NOT NULL
url TEXT NOT NULL
secret_header TEXT -- optional header name + value for auth
owner_user_id TEXT
enabled BOOLEAN DEFAULT TRUE
created_at TEXT
mcp_servers¶
MCP client configurations.
id TEXT PRIMARY KEY
name TEXT NOT NULL -- used for tool namespacing
url TEXT NOT NULL
transport TEXT DEFAULT 'sse'
api_key_enc TEXT -- encrypted
headers_enc TEXT -- encrypted (additional headers)
user_id TEXT -- NULL = global (admin-only)
enabled BOOLEAN DEFAULT TRUE
created_at TEXT
Monitoring tables¶
watched_pages¶
CSS-selector page change monitors.
id TEXT PRIMARY KEY
url TEXT NOT NULL
schedule TEXT NOT NULL -- cron expression
css_selector TEXT -- NULL = full page hash
last_content_hash TEXT
last_changed_at TEXT
last_checked_at TEXT
agent_id TEXT
notification_mode TEXT DEFAULT 'agent' -- 'agent' | 'push'
owner_user_id TEXT
enabled BOOLEAN DEFAULT TRUE
created_at TEXT
rss_feeds¶
RSS/Atom feed monitors.
id TEXT PRIMARY KEY
url TEXT NOT NULL
schedule TEXT NOT NULL
agent_id TEXT
seen_item_ids JSONB DEFAULT '[]'
max_items_per_run INTEGER DEFAULT 5
last_fetched_at TEXT
last_etag TEXT -- HTTP ETag for conditional fetching
last_modified TEXT -- HTTP Last-Modified header
notification_mode TEXT DEFAULT 'agent'
owner_user_id TEXT
enabled BOOLEAN DEFAULT TRUE
created_at TEXT
mfa_challenges¶
TOTP second-factor login sessions.
id TEXT PRIMARY KEY
token TEXT UNIQUE NOT NULL
user_id TEXT NOT NULL
next_url TEXT DEFAULT '/'
created_at TEXT NOT NULL
expires_at TEXT NOT NULL
attempts INTEGER DEFAULT 0
brain database¶
The brain database is separate and uses the pgvector extension.
documents¶
document_id TEXT PRIMARY KEY
content TEXT NOT NULL
embedding vector(1536) -- OpenAI text-embedding-3-small dimensions
metadata JSONB
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
Index: ivfflat or hnsw on embedding for approximate nearest-neighbour search.
asyncpg JSONB gotcha¶
asyncpg does not automatically serialize Python dicts to PostgreSQL JSONB. Every connection must register custom codecs:
await conn.set_type_codec("jsonb", encoder=json.dumps, decoder=json.loads, schema="pg_catalog")
await conn.set_type_codec("json", encoder=json.dumps, decoder=json.loads, schema="pg_catalog")
This is done in database.py::_init_connection() which is passed to asyncpg.create_pool(init=_init_connection).