Documentation
¶
Overview ¶
Package sqlite stores agentsafe runs in SQLite (pure Go: modernc.org/sqlite, no cgo).
db, err := sqlite.Open("runs.db")
r := &agentsafe.Runner{Log: &agentsafe.Journal{Store: db.Run("payout-2026-10-06")}, ...}
Many runs share one database. Each run's lines are rows keyed by (run_id, seq); appending line N is a single statement, inside a write transaction, that inserts it only if the run's last line is N-1: the conditional append that fences stale writers (agentsafe.ErrConflict). Each run also has a lease row (agentsafe.Locker): held by one runner, renewed in the background, and expiring on its own if the holder dies.
Index ¶
Examples ¶
Constants ¶
const DefaultLeaseTTL = 30 * time.Second
DefaultLeaseTTL is how long a lease outlives its holder's last renewal.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type DB ¶
type DB struct {
// LeaseTTL is how long a lease survives without renewal, i.e. how long a crashed runner blocks its run.
// Renewal happens every LeaseTTL/3. 0 = DefaultLeaseTTL.
LeaseTTL time.Duration
// contains filtered or unexported fields
}
DB is a SQLite database holding many runs.
func New ¶
New uses a database you opened yourself with the "sqlite" driver, and creates the tables if needed. Set busy_timeout, or concurrent writers fail with "database is locked" instead of waiting their turn.
func Open ¶
Open opens (creating if needed) a database file with the settings agentsafe relies on: WAL, every commit flushed to stable storage (synchronous=FULL, plus fullfsync: on macOS a plain fsync leaves the commit in the drive's cache, where a power cut loses it; other platforms ignore the pragma), and waiting up to 10s for another writer instead of failing.
Example ¶
Many runs in one SQLite file; each run is fenced (one writer per event) and leased (one runner at a time).
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/Ashutosh2308Bhardwaj/agentsafe"
"github.com/Ashutosh2308Bhardwaj/agentsafe/sqlite"
)
func main() {
dir, _ := os.MkdirTemp("", "agentsafe-sqlite")
defer func() { _ = os.RemoveAll(dir) }()
db, err := sqlite.Open(filepath.Join(dir, "runs.db"))
if err != nil {
panic(err)
}
defer func() { _ = db.Close() }()
r, err := agentsafe.New(&agentsafe.ScriptedModel{Final: "nothing to do"},
&agentsafe.Journal{Store: db.Run("payouts-2026-10-06")})
if err != nil {
panic(err)
}
st, err := r.Start(context.Background(), "sys", "task")
fmt.Println(st.Status, err)
}
Output: finished <nil>
type Run ¶
type Run struct {
// contains filtered or unexported fields
}
Run is one run's lines and lease.
func (*Run) AppendLine ¶
AppendLine implements agentsafe.LineStore: line seq is stored only if the run has exactly seq-1 lines.
func (*Run) Lock ¶
Lock implements agentsafe.Locker: it takes the run's lease if it's free or expired, and renews it in the background until unlock. If this process dies, renewal stops and the lease expires after LeaseTTL. A lease can also be lost while held (this process paused longer than the TTL): another runner may then take the run, and this one is stopped by the conditional append (ErrConflict) at its next write.