---
title: PostgreSQL Store
description: Production-ready persistence with PostgreSQL, grove ORM, and programmatic migrations.
---

The `store/postgres` package provides a production-ready implementation of the composite `store.Store` interface using PostgreSQL with the [grove](https://github.com/xraph/grove) ORM and the PostgreSQL driver.

## Setup

```go
import (
    "github.com/xraph/grove"
    "github.com/xraph/grove/drivers/pgdriver"
    "github.com/xraph/sentinel/store/postgres"
)

db, err := grove.Open(pgdriver.Open("postgres://user:pass@localhost:5432/sentinel?sslmode=disable"))
if err != nil {
    log.Fatal(err)
}

pgStore := postgres.New(db)

// Run migrations
if err := pgStore.Migrate(ctx); err != nil {
    log.Fatal(err)
}
```

## Tables

The PostgreSQL store creates the following tables:

| Table | Entity | Key columns |
|-------|--------|-------------|
| `sentinel_suites` | Suites | `id`, `app_id`, `name`, `system_prompt`, `model`, `persona_ref`, `metadata` (JSONB) |
| `sentinel_cases` | Cases | `id`, `suite_id`, `name`, `input`, `expected`, `scenario_type`, `scorers` (JSONB), `tags` (JSONB) |
| `sentinel_runs` | Runs | `id`, `suite_id`, `app_id`, `model`, `state`, `pass_rate`, `avg_score`, `dimension_scores` (JSONB) |
| `sentinel_results` | Results | `id`, `run_id`, `case_id`, `status`, `score`, `output`, `scorer_results` (JSONB), `run_trace` (JSONB) |
| `sentinel_baselines` | Baselines | `id`, `suite_id`, `run_id`, `name`, `results` (JSONB), `dimension_scores` (JSONB), `is_current` |
| `sentinel_prompt_versions` | Prompt versions | `id`, `suite_id`, `version`, `system_prompt`, `changelog`, `is_current` |

## JSONB columns

Complex nested structures are stored as JSONB columns:

- Case scorers and tags → `scorers JSONB`, `tags JSONB`
- Result scorer outputs and run trace → `scorer_results JSONB`, `run_trace JSONB`
- Dimension scores → `dimension_scores JSONB`
- All entities → `metadata JSONB`

## Internals

| Aspect | Detail |
|--------|--------|
| Driver | grove ORM + pgdriver |
| Migrations | grove orchestrator with programmatic migrations |
| Transactions | PostgreSQL-level transactions |
| Concurrency | Full MVCC with row-level locking |

## Composite store interface

The PostgreSQL store implements all 5 sub-interfaces:

```go
type Store interface {
    suite.Store         // 6 methods
    testcase.Store      // 7 methods
    evalrun.Store       // 5 methods
    baseline.Store      // 5 methods
    promptversion.Store // 5 methods

    Migrate(ctx context.Context) error
    Ping(ctx context.Context) error
    Close() error
}
```

## Embedded migrations

Migrations are registered programmatically via grove's migration orchestrator and run automatically when `Migrate()` is called. There are 5 migration steps:

1. `create_suites` — Creates `sentinel_suites`
2. `create_cases` — Creates `sentinel_cases`
3. `create_runs_and_results` — Creates `sentinel_runs` and `sentinel_results`
4. `create_baselines` — Creates `sentinel_baselines`
5. `create_prompt_versions` — Creates `sentinel_prompt_versions`

## Other store backends

| Backend | Package | Use case |
|---------|---------|----------|
| PostgreSQL | `store/postgres` | Production |
| SQLite | `store/sqlite` | Development, testing, single-machine deployments |
| MongoDB | `store/mongo` | Document-oriented and horizontally-scalable deployments |
| Memory | `store/memory` | Unit tests, ephemeral evaluations |

All backends implement the same `store.Store` interface identically.

## Usage with engine

```go
eng, err := engine.New(
    engine.WithStore(pgStore),
)
```
