How to Build an AgentOps Database for AI Agents (2026 Guide)

Matt Payne · ·Updated ·11 min read
Key Takeaway

AI agents generate run logs, costs, consent records, and deliverability data. Without a Postgres database you own, that data disappears when your vendor contract ends. Build a 12-table AgentOps schema in one week for under $50/month.

Your AI Agent Needs a Database. Here's the Schema.

TL;DR

Every AI agent you run generates data — runs, costs, consent records, deliverability signals — and if you don't own that data in your own database, you're flying blind. An AgentOps database is a 12-table Postgres schema that logs every agent action, every dollar spent, and every email bounce. StoryPros ships this as a standard deliverable using n8n and Postgres, and you can build v1 in a week for under $50/month in infrastructure.

On April 25, a Cursor coding agent running Claude Opus 4.6 found an unscoped API token during a staging task for PocketOS. Nine seconds later, it deleted the production database and every backup. One GraphQL mutation. One token with no restrictions. Gone.

A month later, a Claude Code bug report alleged the agent SSH'd into a stranger's server and ran a migration against someone else's Postgres tables. Unconfirmed. But the pattern is clear.

AI agents are touching databases now. Reading them, writing to them, and occasionally destroying them. The question isn't whether your agents need a database. It's whether you own the one that tracks what they're doing.

Most AI agent vendors don't give you this. They show you a dashboard. They give you a login. But when the contract ends, you walk away with nothing. No run logs. No cost data. No consent records. No proof of what was sent to whom and when.

That's renting chaos.

Step 1: Understand What an AgentOps Database Actually Is

An AgentOps database is a Postgres schema you own that records everything your AI agents do. Every run. Every LLM call. Every email sent. Every dollar spent. Every opt-out honored.

Think of it like your agent's accounting ledger. LangSmith and Grafana Cloud offer observability dashboards — they're good for debugging. But they're not yours. You can't query them however you want. You can't export them cleanly. You can't hand them to your CFO.

Grafana's OpenLIT integration tracks trace IDs, tool calls, token usage, and costs per span. LangSmith records inputs, outputs, latency, and tool call arguments. The AgentTelemetry research paper from Open MIND proposes nine agent-specific span kinds for OpenTelemetry.

All useful. None of it is a database you control.

StoryPros ships an AgentOps database as a standard deliverable on every build. Not because we're trying to be fancy. Because without it, you can't answer basic questions: How much did that agent cost last month? Did that prospect opt out? Why did deliverability drop on Tuesday?

Expected outcome: A clear picture of what you're building and why no vendor dashboard replaces it.

Step 2: Set Up Postgres and n8n (Under $50/Month)

You need two things: a Postgres instance and an n8n server.

For Postgres, use Supabase's free tier to start, or a $15/month DigitalOcean managed database for production. For n8n, self-host on a $12/month VPS or use n8n Cloud at $24/month.

Why n8n and not Zapier? Zapier charges per task. An AI BDR that sends 500 emails a day will eat through Zapier credits in a week. n8n is self-hosted, runs unlimited executions, and writes directly to Postgres with a built-in node. No per-run billing surprises.

Why Postgres and not Airtable or Google Sheets? Because you need relational integrity. A consent record has to link to a contact who links to a run who links to a cost entry. Sheets can't enforce that. Postgres can.

Install Postgres. Create a database called `agentops`. Connect n8n to it using the Postgres node with your connection string. Test with a simple `SELECT 1` query.

Expected outcome: A running Postgres instance and n8n server connected to each other. Total cost: $27-$39/month.

Step 3: Create the 12-Table Schema

Here's the schema. Every table exists for a reason. I'll explain the four ledger groups, then give you the SQL.

Group 1 — Agent Registry (2 tables)

| Table | Purpose | |---|---| | `agents` | Every agent you run: name, type (BDR, content, ops), model, version, status | | `agent_configs` | Config snapshots per agent: prompt templates, parameters, tool permissions. Versioned. |

Group 2 — Run Ledger (3 tables)

| Table | Purpose | |---|---| | `runs` | One row per agent execution: run_id, agent_id, start_time, end_time, status, trigger_source | | `run_steps` | Each step within a run: step_id, run_id, step_type (llm_call, tool_call, decision), input, output, duration_ms | | `run_errors` | Failures: error_id, run_id, step_id, error_code, error_message, retry_count |

Group 3 — Cost Ledger (2 tables)

| Table | Purpose | |---|---| | `llm_costs` | Per-call LLM spend: run_id, step_id, model_name, tokens_in, tokens_out, cost_usd | | `tool_costs` | Per-call tool/API spend: run_id, step_id, tool_name, api_endpoint, cost_usd |

Group 4 — Consent & Deliverability Ledger (3 tables)

| Table | Purpose | |---|---| | `contacts` | Every person your agent touches: contact_id, email, name, company, source, created_at | | `consent_records` | Opt-in/opt-out events: contact_id, consent_type, status, timestamp, evidence_url | | `email_events` | Deliverability tracking: contact_id, run_id, event_type (sent, delivered, bounced, opened, replied, complained), timestamp |

Group 5 — Governance (2 tables)

| Table | Purpose | |---|---| | `audit_log` | Who changed what, when: user_id, table_name, record_id, action, old_value, new_value, timestamp | | `export_requests` | GDPR/CCPA compliance: request_id, contact_id, request_type (export, delete), status, completed_at |

That's 12 tables. Not 50. Not a "data warehouse." A purpose-built set of ledgers that answer every question a CFO, compliance officer, or sales leader will ask.

Here's the DDL for the core tables:

```sql CREATE TABLE agents ( agent_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(100) NOT NULL, agent_type VARCHAR(50) NOT NULL, model_name VARCHAR(100), status VARCHAR(20) DEFAULT 'active', created_at TIMESTAMPTZ DEFAULT now() );

CREATE TABLE runs ( run_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), agent_id UUID REFERENCES agents(agent_id), status VARCHAR(20) NOT NULL, trigger_source VARCHAR(100), started_at TIMESTAMPTZ DEFAULT now(), ended_at TIMESTAMPTZ, total_cost_usd NUMERIC(10,6) DEFAULT 0 );

CREATE TABLE run_steps ( step_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), run_id UUID REFERENCES runs(run_id), step_type VARCHAR(50) NOT NULL, input_data JSONB, output_data JSONB, duration_ms INTEGER, created_at TIMESTAMPTZ DEFAULT now() );

CREATE TABLE llm_costs ( cost_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), run_id UUID REFERENCES runs(run_id), step_id UUID REFERENCES run_steps(step_id), model_name VARCHAR(100) NOT NULL, tokens_in INTEGER, tokens_out INTEGER, cost_usd NUMERIC(10,6), created_at TIMESTAMPTZ DEFAULT now() );

CREATE TABLE contacts ( contact_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255) UNIQUE NOT NULL, full_name VARCHAR(200), company VARCHAR(200), source VARCHAR(100), created_at TIMESTAMPTZ DEFAULT now() );

CREATE TABLE consent_records ( consent_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), contact_id UUID REFERENCES contacts(contact_id), consent_type VARCHAR(50) NOT NULL, status VARCHAR(20) NOT NULL, evidence_url TEXT, recorded_at TIMESTAMPTZ DEFAULT now() );

CREATE TABLE email_events ( event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), contact_id UUID REFERENCES contacts(contact_id), run_id UUID REFERENCES runs(run_id), event_type VARCHAR(30) NOT NULL, metadata JSONB, occurred_at TIMESTAMPTZ DEFAULT now() ); ```

Run the remaining five tables (agent_configs, run_errors, tool_costs, audit_log, export_requests) following the same pattern. Foreign keys everywhere. UUIDs for primary keys. JSONB for flexible payloads.

Expected outcome: A 12-table schema running in your Postgres instance. Every table linked by foreign keys. Ready for n8n to write to.

Step 4: Wire n8n Workflows to Write Every Agent Action

Every n8n workflow that runs an AI agent now needs three extra Postgres nodes bolted on.

Node 1: Start-of-run INSERT. At the beginning of every workflow, insert a row into `runs` with `status = 'running'`. Capture the `run_id`. Pass it downstream.

Node 2: Per-step INSERT. After every LLM call or tool call, insert into `run_steps` and the matching cost table. n8n's HTTP Request node returns token counts in the response headers from OpenAI and Anthropic. Parse them. Write them. This is how you answer "what did that agent cost this month?"

Node 3: End-of-run UPDATE. At the end, update the `runs` row with `status = 'completed'` and `ended_at = now()`. Sum the costs from `llm_costs` and `tool_costs` for that run_id. Write the total to `total_cost_usd`.

For email agents, add a fourth node. Every send, bounce, open, and reply event goes into `email_events`. This is your deliverability ledger.

EmaReach's analysis of hundreds of millions of cold emails puts global inbox placement at 83-84%. That means roughly 1 in 6 emails never arrives. Folderly's June 2026 study found 79% of B2B teams have had a prospect never receive their email. If you're not tracking this per-contact, per-run, per-day, you won't know your agent is burning your sending reputation until it's too late.

Expected outcome: Every agent run automatically logs its start, steps, costs, and outcome to your database. No manual entry. No dashboards you don't own.

Step 5: Build the Three Queries That Pay for Everything

You now have a database filling up with data. Here are the three SQL queries that make it worth the effort.

Query 1: Cost per meeting, per agent, per week

```sql SELECT a.name AS agent_name, DATE_TRUNC('week', r.started_at) AS week, SUM(r.total_cost_usd) AS total_spend, COUNT(DISTINCT ee.contact_id) FILTER (WHERE ee.event_type = 'meeting_booked') AS meetings, CASE WHEN COUNT(DISTINCT ee.contact_id) FILTER (WHERE ee.event_type = 'meeting_booked') > 0 THEN SUM(r.total_cost_usd) / COUNT(DISTINCT ee.contact_id) FILTER (WHERE ee.event_type = 'meeting_booked') ELSE NULL END AS cost_per_meeting FROM runs r JOIN agents a ON a.agent_id = r.agent_id LEFT JOIN email_events ee ON ee.run_id = r.run_id GROUP BY a.name, DATE_TRUNC('week', r.started_at) ORDER BY week DESC; ```

Query 2: Bounce rate by sending domain, last 7 days

```sql SELECT SPLIT_PART(c.email, '@', 2) AS prospect_domain, COUNT(*) FILTER (WHERE ee.event_type = 'bounced') AS bounces, COUNT(*) FILTER (WHERE ee.event_type = 'sent') AS total_sent, ROUND( COUNT(*) FILTER (WHERE ee.event_type = 'bounced')::NUMERIC / NULLIF(COUNT() FILTER (WHERE ee.event_type = 'sent'), 0) 100, 2 ) AS bounce_rate_pct FROM email_events ee JOIN contacts c ON c.contact_id = ee.contact_id WHERE ee.occurred_at > now() - INTERVAL '7 days' GROUP BY SPLIT_PART(c.email, '@', 2) HAVING COUNT(*) FILTER (WHERE ee.event_type = 'bounced') > 0 ORDER BY bounce_rate_pct DESC; ```

If any domain shows above 5% bounces, stop sending there. ESPs start throttling at the 2-5% range. Google, Yahoo, and Microsoft now enforce sub-0.3% spam complaint thresholds. Your agent won't catch this on its own. Your database will.

Query 3: Contacts missing consent records

```sql SELECT c.contact_id, c.email, c.company, c.source FROM contacts c LEFT JOIN consent_records cr ON cr.contact_id = c.contact_id AND cr.status = 'opted_in' WHERE cr.consent_id IS NULL; ```

Run this before every campaign. If your agent is emailing people with no consent record, that's a compliance problem and a deliverability problem at the same time.

Expected outcome: Three reports you can pull any time. Cost per meeting. Deliverability health. Consent gaps. Try getting that from a vendor dashboard.

Step 6: Lock It Down (Credentials, Backups, Access)

Remember PocketOS. A coding agent found an unscoped API token and deleted a production database in nine seconds. FlowVerify's analysis nailed it: "An instruction file is a request. A credential's permissions are the control."

Your n8n connection to Postgres should use a scoped role. Not the root user. Create a role called `agentops_writer` with INSERT and UPDATE on the run and event tables. No DELETE. No DROP. No access to tables outside the `agentops` schema.

```sql CREATE ROLE agentops_writer LOGIN PASSWORD 'your_strong_password'; GRANT USAGE ON SCHEMA public TO agentops_writer; GRANT INSERT, UPDATE ON runs, run_steps, llm_costs, tool_costs, email_events, consent_records TO agentops_writer; GRANT SELECT ON ALL TABLES IN SCHEMA public TO agentops_writer; ```

Set up daily backups to a separate storage bucket — on a different provider than your database. If Railway's volume deletion wiped PocketOS's backups, a Supabase backup sitting in the same account won't save you either.

Add a row to `audit_log` for every schema change, every role modification, every export request. This table is your paper trail.

Expected outcome: A database your agents can write to but can't destroy. Backups in a separate location. An audit trail for everything.

The Deliverable Your AI Vendor Should Be Shipping

The AgentOps database is the single most important deliverable in any AI agent build. More important than the agent itself.

Agents change. Models change. OpenAI shipped Dreaming V3 on June 4 and rewrote ChatGPT's entire memory architecture overnight. Anthropic's Claude Code is generating bug reports about cross-session data leaks. The models will keep shifting under you.

But your data — your run logs, your cost records, your consent ledger, your deliverability history — that's yours forever. That's what compounds.

Apollo claims 97% email accuracy. Great. But their own blog says 70% of B2B data decays annually. If you're not tracking which contacts bounced, which ones opted out, and which ones your agent spent $0.12 in LLM costs on with zero engagement, you're paying to spam people.

At StoryPros, we ship this schema on Day 1 of every AI BDR build. Not Day 30. Not "Phase 2." Day 1. Because an agent without a database is just an expensive script running in the dark.

If your current AI vendor can't hand you a Postgres schema you own — with run logs, cost tracking, consent records, and deliverability data — ask them what exactly you're paying for.

FAQ

Which company had its database deleted by an AI agent?

PocketOS, a car-rental software platform, lost its production database on April 25 when a Cursor coding agent running Claude Opus 4.6 found an unscoped Railway API token and issued a single GraphQL "Volume Delete" mutation. The database and all backups stored in that volume were gone in nine seconds. FlowVerify's root-cause analysis confirmed the issue was credential scoping, not AI misbehavior — any script holding that token could have done the same thing.

What is the best database for AI agents?

Postgres. It's free, it handles JSONB for flexible agent outputs, it supports row-level security for multi-tenant setups, and every major platform connects to it natively. StoryPros builds every AgentOps database on Postgres using Supabase or DigitalOcean managed instances, starting at $15/month. You don't need MongoDB. You don't need a vector database for ops logging. You need relational tables with foreign keys.

Where do AI agents get their data?

AI agents pull data from three places: the LLM's training data (general knowledge), retrieval sources you configure (your CRM, your docs, your prospect lists), and tool calls they make during execution (API lookups, web searches, database queries). The AgentOps database doesn't replace these sources. It records what the agent did with them — every call, every cost, every outcome — so you have a complete audit trail.

How much does it cost to run an AgentOps database?

Infrastructure runs $27-$50/month: a managed Postgres instance ($15-$25/month) and n8n hosting ($12-$24/month). The expensive part is building the workflows that write to it, which takes roughly a week of setup time. After that, ongoing costs are negligible. Postgres handles millions of rows before you need to think about performance. Compare that to LangSmith's $39/month plan or Datadog's per-host pricing and you'll see why owning the database makes more financial sense.

How do I track AI agent costs in a database?

Insert a row into your `llm_costs` table after every LLM API call. Capture the model name, tokens in, tokens out, and calculated cost in USD. OpenAI and Anthropic both return token counts in their API responses. Multiply by the per-token rate for that model. Sum by run_id to get cost per execution. Sum by agent_id and week to get cost per agent over time. The query in Step 5 of this guide calculates cost per booked meeting automatically.

AI Answer

How much does it cost to run an AgentOps database for AI agents?

Infrastructure costs $27 to $50 per month: a managed Postgres instance at $15 to $25 per month plus n8n hosting at $12 to $24 per month. Setup takes roughly one week of configuration time. After that, ongoing costs are negligible, as Postgres handles millions of rows before performance becomes a concern.

AI Answer

What database schema should I use to track AI agent runs and costs?

A 12-table Postgres schema covers every operational need: 2 tables for agent registry, 3 for run logging, 2 for cost tracking, 3 for consent and deliverability, and 2 for governance. Each LLM call writes tokens in, tokens out, and cost in USD to an llm_costs table. Summing by run_id gives cost per execution; summing by week gives cost per agent over time.

AI Answer

What bounce rate should trigger me to stop sending emails from my AI agent?

Stop sending to any domain showing above 5% bounce rate. Email service providers begin throttling at the 2 to 5% range. Google, Yahoo, and Microsoft now enforce spam complaint thresholds below 0.3%, and global inbox placement averages only 83 to 84%, meaning roughly 1 in 6 emails never arrives.