# How no-mistakes Uses Its SQLite Database for Tracking Runs, Rounds, and Findings

> Learn how no-mistakes leverages its SQLite database to efficiently track runs, rounds, and findings, enabling robust execution state persistence and easy querying.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: how-to-guide
- Published: 2026-06-25

---

**no-mistakes persists execution state to a local SQLite file (default `$NM_HOME/state.sqlite`) using three core tables—runs, rounds, and findings—enabling the CLI to record, query, and replay every agent interaction across invocations.**

The `no-mistakes` CLI relies on a durable SQLite database to maintain continuity between commands, storing the complete lifecycle of every execution in a single file. By default, this database resides at `$NM_HOME/state.sqlite` and is managed through a thin abstraction layer in [`internal/db/db.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/db.go). This architecture allows the tool to track individual CLI invocations as **runs**, group agent work into **rounds**, and capture specific observations as **findings** that survive across process restarts.

## Database Schema and Core Tables

The schema is defined as a hard-coded string constant (`schemaSQL`) in [`internal/db/db.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/db.go) and applied idempotently every time the database opens. It establishes three interconnected tables that mirror the execution hierarchy of the CLI.

### The runs Table

Each row in `runs` represents a single CLI invocation. Key columns include:

- `id`: A monotonic **ULID** generated by `NewID()` that serves as the primary key
- `started_at` / `ended_at`: Unix timestamps marking the run's lifespan  
- `status`: Current state of the execution (e.g., "running", "completed", "failed")

### The rounds Table

Rounds group the work performed by a single agent during a run. The table structure includes:

- `id`: ULID primary key
- `run_id`: Foreign key referencing `runs.id`
- `seq`: Sequence number ordering the round within its parent run
- `started_at` / `ended_at`: Timing boundaries for the agent's execution step

This design allows a single run to contain multiple sequential rounds, such as distinct steps for Codex or Claude agent invocations.

### The findings Table

Individual observations returned by agents are stored as findings. Important columns include:

- `id`: ULID primary key
- `round_id`: Foreign key linking to the specific round that produced the finding
- `type`: Category of observation (e.g., "lint", "test_failure", "suggestion")
- `message`: Detailed description of the issue or observation
- `source`: Origin of the finding (e.g., "golint", "pytest")

## Connection Configuration and Reliability

The database connection is configured with specific SQLite pragmas to ensure data integrity under concurrent access. In [`internal/db/db.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/db.go), the connection string includes:

- **`&_pragma=foreign_keys(on)`**: Enforces referential integrity between runs, rounds, and findings
- **`&_pragma=journal_mode(wal)`**: Enables Write-Ahead Logging for concurrent reader access during writes  
- **`&_pragma=busy_timeout(5000)`**: Sets a 5-second timeout to handle race conditions when multiple goroutines insert findings simultaneously

## Initialization and Migration Strategy

When the CLI starts, it calls `db.Open(p.DB())` where `p.DB()` (from [`internal/paths/paths.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/paths/paths.go)) returns the absolute path to `state.sqlite`. The `Open` function creates the file if missing and executes the embedded `schemaSQL` migration.

Additional `migrationStatements` are applied to maintain forward compatibility, with duplicate-column errors explicitly ignored to ensure idempotency. This approach allows the database schema to evolve without requiring separate migration files or versioning tables.

## Runtime Usage and Code Examples

During execution, pipeline steps interact with the database using standard `database/sql` patterns. The `DB` struct wraps `*sql.DB` and exposes only `Open` and `Close` methods, while higher-level CRUD operations reside in the pipeline packages and [`internal/daemon/manager.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/manager.go) (which persists daemon-wide state such as active runs).

The following example demonstrates the typical lifecycle:

```go
// Open the persistent database (creates it on first use)
paths := internal.Paths{Root: os.Getenv("NM_HOME")}
db, err := db.Open(paths.DB())
if err != nil {
    log.Fatalf("cannot open DB: %v", err)
}
defer db.Close()

// Insert a new run (simplified)
runID := db.NewID()
_, err = db.sql.Exec(`
    INSERT INTO runs (id, started_at, status)
    VALUES (?, ?, ?)`, runID, time.Now().Unix(), "running")
if err != nil {
    log.Fatalf("run insert failed: %v", err)
}

// Insert a round belonging to the run
roundID := db.NewID()
_, err = db.sql.Exec(`
    INSERT INTO rounds (id, run_id, seq, started_at)
    VALUES (?, ?, ?, ?)`,
    roundID, runID, 1, time.Now().Unix(),
)
if err != nil {
    log.Fatalf("round insert failed: %v", err)
}

// Record a finding produced by an agent in that round
_, err = db.sql.Exec(`
    INSERT INTO findings (id, round_id, type, message, source)
    VALUES (?, ?, ?, ?, ?)`,
    db.NewID(), roundID, "lint", "unused variable x", "golint",
)
if err != nil {
    log.Fatalf("finding insert failed: %v", err)
}

```

The e2e test suite in [`internal/e2e/journey_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/e2e/journey_test.go) validates this persistence by opening the database via `p.DB()` and verifying that runs, rounds, and findings can be queried after insertion. Similarly, [`internal/pipeline/steps/intent_integration_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/intent_integration_test.go) demonstrates how the intent step records rounds and findings during agent execution.

## Summary

- **no-mistakes** stores execution history in `$NM_HOME/state.sqlite`, managed through [`internal/db/db.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/db.go)
- The schema consists of three tables—**runs**, **rounds**, and **findings**—linked by ULID foreign keys
- SQLite pragmas enforce foreign keys, WAL mode, and busy timeouts for reliable concurrent access
- ULID identifiers provide sortable, globally unique keys without auto-increment columns
- The database persists across CLI invocations, enabling commands like `no-mistakes doctor` and `no-mistakes log` to query historic data

## Frequently Asked Questions

### Where is the SQLite database file located?

By default, the database is stored at `$NM_HOME/state.sqlite`. The exact path is determined by [`internal/paths/paths.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/paths/paths.go), which resolves the `DB()` method to return the absolute file path within the no-mistakes home directory.

### How does no-mistakes handle schema changes?

The tool applies a hard-coded migration script (`schemaSQL`) on every `Open` call, with additional `migrationStatements` for forward compatibility. Duplicate-column errors are ignored to ensure idempotent execution, allowing the schema to evolve without manual migration steps.

### Why does the database use ULIDs instead of auto-incrementing integers?

The `NewID()` function generates monotonic ULIDs for every row, providing globally unique, sortable identifiers that do not require an additional auto-increment column. This approach simplifies distributed ID generation and ensures chronological ordering when querying the tables.

### Can multiple processes access the database simultaneously?

Yes. The connection string enables Write-Ahead Logging (`journal_mode(wal)`) and sets a 5-second busy timeout (`busy_timeout(5000)`), allowing concurrent readers while the CLI writes new findings. Foreign key constraints are also enforced at the database level to maintain referential integrity.