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

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. 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 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, 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) 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 (which persists daemon-wide state such as active runs).

The following example demonstrates the typical lifecycle:

// 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 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 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
  • 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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →