How no-mistakes Stores Run State in SQLite: Inside internal/db

no-mistakes persists every pipeline execution in a local SQLite database using the runs table defined in internal/db/schema.go, with atomic helper functions in internal/db/run.go managing the lifecycle from pending to completed.

The no-mistakes repository implements a zero-configuration SQLite storage layer to maintain durable records of pipeline runs. Located in the internal/db package, this module captures the entire execution lifecycle—from initial creation through terminal states—without requiring external database infrastructure. Understanding how the runs table schema and the data-access layer manage state transitions is essential for extending the executor or debugging run persistence.

The runs Table Schema in internal/db/schema.go

Core Identifier and Git Reference Columns

The runs table is created by the migrateRuns() function in internal/db/schema.go using an INTEGER PRIMARY KEY AUTOINCREMENT for unique run identification. It stores Git context through four critical columns: repo_id (a foreign key to the repos table), branch, head_sha (the commit SHA checked out when the run started), and base_sha (the commit SHA of the remote default at run creation). These columns ensure every run record is immutably tied to specific repository state and version control metadata.

Status and Temporal Tracking

Run progression is tracked through the status column, which accepts enumerated values: pending, running, passed, failed, or cancelled. Temporal tracking uses started_at and completed_at datetime fields. The nullable awaiting_agent_since column specifically marks when a run is parked at a gate awaiting human or automated agent review, enabling the CLI to render "parked" badges without polling the executor process.

Flexible JSON Payloads

The schema accommodates extensible data through two JSON text columns. The result column stores the final execution summary—including step outcomes and diagnostics—while metadata provides a free-form field for plugin-specific data. This design allows agents to persist arbitrary structured data without requiring schema migrations.

State Lifecycle Management in internal/db/run.go

The internal/db/run.go file provides thin wrappers around sqlx queries that encapsulate all reads and writes to the runs table.

Run Creation and Initialization

When a user initiates a pipeline via no-mistakes run, the executor calls InsertRun(). This function populates repo_id, branch, head_sha, and base_sha, setting status to "pending" and recording started_at. The function returns the generated id for subsequent state updates.

Transitioning to Active Execution

As the executor begins processing steps, it invokes SetRunStatus(ctx, id, "running") to update the status column. This transition marks the shift from queued to active execution, allowing the CLI to display real-time run activity.

Agent Gate Handling

Gate events utilize the awaiting_agent_since column for persistent parking state. When a run reaches a review gate, SetRunAwaitingAgent(ctx, id, now) populates this timestamp. Once the agent resolves the gate, ClearRunAwaitingAgent(ctx, id) nulls the field, guaranteeing the flag is only set while the run is genuinely awaiting an agent.

Terminal State Persistence

Completion requires three coordinated operations: SetRunStatus() updates the terminal state ("passed" or "failed"), SetRunCompletedAt() records the finish timestamp, and UpdateRunResult() persists the JSON-encoded execution summary. These operations ensure atomic state capture for post-run analysis.

Database Initialization and Configuration

The SQLite database file resides at $NM_HOME/state.db, initialized by db.New() in internal/db/db.go. This function enables the foreign_keys = ON pragma for referential integrity between runs.repo_id and repos.id, and enables WAL mode for concurrent read/write performance. The migrateRuns() function executes the CREATE TABLE statement if the runs table does not exist.

The runs table participates in a relational schema managed by internal/db/schema.go. The repos table stores repository configuration, while rounds groups steps that belong to the same retryable execution batch. Individual steps maintain their own status records in a separate table, and intent_cache persists agent analysis to avoid recomputation. Foreign key constraints ensure that runs cannot reference non-existent repositories.

Practical Code Examples

Creating and Updating a Run

// Create a new run (invoked when a user runs `no-mistakes run …`)
runID, err := db.InsertRun(ctx, internaldb.Run{
    RepoID:     repo.ID,
    Branch:     branch,
    HeadSHA:    headSHA,
    BaseSHA:    baseSHA,
    Status:     "pending",
    StartedAt:  time.Now(),
})
if err != nil {
    log.Fatalf("cannot create run: %v", err)
}

// Mark the run as awaiting an agent (e.g. at a review gate)
if err := db.SetRunAwaitingAgent(ctx, runID, time.Now()); err != nil {
    log.Fatalf("cannot set awaiting‑agent flag: %v", err)
}

// When the agent replies, clear the flag
if err := db.ClearRunAwaitingAgent(ctx, runID); err != nil {
    log.Fatalf("cannot clear awaiting‑agent flag: %v", err)
}

// Finish the run – store final status and result JSON
finalResult := map[string]any{
    "steps": stepSummaries,
}
resultJSON, _ := json.Marshal(finalResult)

if err := db.UpdateRunResult(ctx, runID, string(resultJSON)); err != nil {
    log.Fatalf("cannot store result: %v", err)
}
if err := db.SetRunStatus(ctx, runID, "passed"); err != nil {
    log.Fatalf("cannot set final status: %v", err)
}
if err := db.SetRunCompletedAt(ctx, runID, time.Now()); err != nil {
    log.Fatalf("cannot set completed_at: %v", err)
}

Querying Current Run State

// Query the current run state (used by `axi status`)
run, err := db.GetRun(ctx, runID)
if err != nil {
    log.Fatalf("run not found: %v", err)
}
fmt.Printf("Run %d – %s (awaiting agent: %v)\n",
    run.ID, run.Status, run.AwaitingAgentSince != nil)

Summary

  • The runs table in internal/db/schema.go defines the canonical schema for pipeline execution state, including Git refs, timestamps, and JSON result payloads.
  • State transitions are atomic operations handled by internal/db/run.go functions like SetRunStatus, SetRunAwaitingAgent, and UpdateRunResult.
  • The awaiting_agent_since column enables persistent gate-tracking without process polling, supporting the "parked" badge rendered by internal/cli/axi_render.go.
  • SQLite provides zero-configuration persistence at $NM_HOME/state.db with foreign key enforcement and WAL mode enabled in db.New().
  • Related tables (repos, rounds, steps) provide relational context while JSON columns ensure schema flexibility for agent-specific data.

Frequently Asked Questions

Where is the SQLite database file located?

The database file is stored at $NM_HOME/state.db, where $NM_HOME resolves to the user's home directory or a configured environment path. This location is determined during initialization in internal/db/db.go.

How does the executor differentiate between a run that is running versus one awaiting an agent?

The executor distinguishes these states through the status column and awaiting_agent_since timestamp. A run with status = "running" and a non-null awaiting_agent_since indicates active execution paused at a gate, while status = "pending" indicates the run has not yet started processing steps.

Can I query the run state directly using the sqlite3 command-line tool?

Yes, you can open $NM_HOME/state.db with any SQLite client. The runs table contains human-readable columns like head_sha and status, though the result and metadata columns contain JSON text that requires parsing or filtering using SQLite's JSON1 extension functions.

What happens if the result JSON exceeds SQLite's storage limits?

SQLite's TEXT type has a theoretical limit of 2GB, which is sufficient for typical pipeline summaries. The no-mistakes architecture treats the result column as a completion blob rather than granular step logs, with detailed logs potentially stored elsewhere or referenced by ID to avoid size constraints.

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 →