Documentation
¶
Overview ¶
Package sqlite is a session store over one SQLite database file. It is a nested module so the driver (modernc.org/sqlite, pure Go) stays out of the library's dependency graph. Sessions and entries are rows; an entry row holds the same JSON line the jsonl store would write, so the two stores are interchangeable and a database can be dumped to session files without conversion.
Index ¶
- Variables
- type LockInfo
- type Option
- type Store
- func (s *Store) Append(ctx context.Context, sessionID string, e agentsession.Entry) (string, error)
- func (s *Store) BreakLock(ctx context.Context, id string) error
- func (s *Store) Close() error
- func (s *Store) Create(ctx context.Context, h agentsession.Header) (*agentsession.Session, error)
- func (s *Store) Delete(ctx context.Context, id string) error
- func (s *Store) Follow(ctx context.Context, id string, from agentsession.Cursor) iter.Seq2[agentsession.Change, error]
- func (s *Store) List(ctx context.Context, f agentsession.ListFilter) iter.Seq2[agentsession.Summary, error]
- func (s *Store) ListRefs(ctx context.Context, prefix string) iter.Seq2[agentsession.Ref, error]
- func (s *Store) LockHolder(ctx context.Context, id string) (*LockInfo, error)
- func (s *Store) Open(ctx context.Context, id string) (*agentsession.Session, error)
- func (s *Store) Read(ctx context.Context, id string) (*agentsession.Session, error)
- func (s *Store) RefLog(ctx context.Context, name string) iter.Seq2[agentsession.RefUpdate, error]
- func (s *Store) Release(id string)
- func (s *Store) ResolveRef(ctx context.Context, name string) (agentsession.RefTarget, error)
- func (s *Store) UpdateRef(ctx context.Context, name string, expected, next agentsession.RefTarget, ...) error
Constants ¶
This section is empty.
Variables ¶
var ErrConcurrentWriter = errors.New("sqlite: another process appended to the session")
ErrConcurrentWriter is returned by Append when another process appended to the session since this store loaded it. The store has no cross-process lock, so the conflict is found at the write: the entries table's (session_id, seq) key refuses the line. The session then stays refused in this store until Release reloads it, so a recorder that continued from a transcript the other writer never saw fails loudly rather than re-parenting its entries under a leaf its agent never received.
var ErrSessionLocked = agentsession.ErrSessionLocked
ErrSessionLocked is returned by Create, Open and Append when another process holds the session. The message names the holder, with the times it has held it since and last appended; BreakLock removes a lock the caller has decided is dead.
It is agentsession.ErrSessionLocked, so a host that reads the store through the interface matches the same sentinel whichever store it was given.
Functions ¶
This section is empty.
Types ¶
type LockInfo ¶ added in v0.0.5
type LockInfo struct {
PID int
Host string
Since time.Time
// Heartbeat is when the holder last appended.
Heartbeat time.Time
}
LockInfo describes the holder of a session.
type Option ¶ added in v0.0.5
type Option func(*Store)
Option configures a Store.
func WithFollowInterval ¶ added in v0.0.21
WithFollowInterval sets how often a follower looks at the database for a change no writer in this process rang for: the shortest wait, which it keeps while rows keep arriving and doubles while they do not, up to a second or the interval itself when that is longer. The default is 100 ms. A follower of a session this store appends to is woken by the append and does not wait for the interval.
func WithReadOnly ¶ added in v0.0.6
func WithReadOnly() Option
WithReadOnly opens the store for reading: Open takes no hold on a session, so a session another process holds can be read while it is held, and Create, Append, Delete and BreakLock return agentsession.ErrReadOnly. Nothing in a session is written.
Two things it is not. Opening the database still creates or migrates its tables, which is what makes a file an earlier release wrote readable, so a read-only store writes the database file once at open and takes its write lock while it does; the option cannot change that, since the schema is what the reader reads through. And an open session is cached as it is in a writing store, so a session read while another process appends to it shows what it held when it was opened; call Store.Release and open it again to see the rest. Store.Read, on any store, reads a session without a hold and caches nothing, so it reads what the store holds at each call.
func WithStaleLockReport ¶ added in v0.0.5
WithStaleLockReport sets a function the store calls when Create or Open takes over the hold of a process on this host that no longer runs, with the dead holder's details, as the jsonl store's option of the same name does. The takeover itself is not changed.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is a SQLite-backed agentsession.Store. It is safe for concurrent use within one process. A store opened with WithReadOnly takes no hold and refuses every write, so a session another process is writing can still be read. Several processes may share the file: every write is one immediate transaction, and each open session is held by one process at a time through the holders table, so a second process that opens the same session gets ErrSessionLocked instead of interleaving entries with the first. The hold is released by Release, Delete and Close; a hold left by a process on this host that no longer runs is taken over on the next open, and BreakLock clears one from any other holder. Every append refreshes the holder's heartbeat and fails with ErrSessionLocked if the hold was taken from under it.
func Open ¶
Open opens or creates the database at path and applies the schema. Every connection runs in WAL mode with foreign keys on and a busy timeout, and writes go through a single-connection pool so writers queue instead of failing.
func (*Store) Append ¶
Append implements agentsession.Store: the entry joins the in-memory tree, then its line is inserted in one immediate transaction. If the insert fails the cached session is dropped so the next Open reloads the database's view. The first append to a session whose header names an earlier minor raises the header's format in the same transaction.
func (*Store) BreakLock ¶ added in v0.0.5
BreakLock removes a session's hold whoever has it. Use it when Open reports ErrSessionLocked and the caller has confirmed the holder is gone, for example a process on another host that crashed. Breaking the hold of a live writer makes that writer's next append fail with ErrSessionLocked rather than interleave with the new holder's. A store opened with WithReadOnly refuses: it takes no hold, so it has no business dropping another process's.
func (*Store) Create ¶
func (s *Store) Create(ctx context.Context, h agentsession.Header) (*agentsession.Session, error)
Create implements agentsession.Store.
func (*Store) Follow ¶ added in v0.0.21
func (s *Store) Follow(ctx context.Context, id string, from agentsession.Cursor) iter.Seq2[agentsession.Change, error]
Follow implements agentsession.Follower. The snapshot is what Read returns, from one read transaction, and the tail selects the rows whose seq is above the last the follower has, in order, each poll: a lookup of the session's header and newest seq, then the new rows when there are any. It takes no hold and writes nothing, so a read-only store follows too. An append is visible at its commit, and the database runs with synchronous=NORMAL, so a transaction a power loss takes back may have been followed.
The poll is a query on the pool and not a PRAGMA data_version on a connection of the follower's own, which would keep one connection of the pool per follower and leave none for the store's reads.
A seq below the cursor's, which only a rebuild of the rows makes, is a agentsession.Reset. A session deleted ends the follow with agentsession.ErrNoSession, and so does one created again under its ID, which the stored header tells: its ID, creation stamp and members, but not its format, which the first append of a newer writer raises and which changes nothing a follower holds. The cursor names the last seq and that header's hash.
func (*Store) List ¶
func (s *Store) List(ctx context.Context, f agentsession.ListFilter) iter.Seq2[agentsession.Summary, error]
List implements agentsession.Store.
func (*Store) ListRefs ¶ added in v0.0.21
ListRefs implements agentsession.RefStore.
func (*Store) LockHolder ¶ added in v0.0.5
LockHolder reports who holds a session, or nil when it is free. A session this store holds is reported as held by this process.
func (*Store) Open ¶
Open implements agentsession.Store. A session already open in this store is returned as is.
The hold is the store's, not the caller's: it is kept until Store.Release, Store.Delete or Store.Close, and a Release frees it whoever opened the session. A process that reads sessions it does not write uses Store.Read, or a second store opened with WithReadOnly, so that it neither keeps them from other processes nor frees one its own writer is using.
func (*Store) Read ¶ added in v0.0.19
Read implements agentsession.Reader: it reads the session's rows as a read-only store's Open does, taking no hold and keeping nothing, so a session this store or another process is writing is read and stays the writer's. The session returned is the caller's own, read afresh from the database in one read transaction, even when this store holds the session open, and even when this store has refused it with ErrConcurrentWriter. An entry is there once the Append that wrote it has returned; a leaf moved through Session.Branch is not stored, and is not there. The database runs with synchronous=NORMAL, so a transaction a power loss takes back may have been read; a process crash takes back none.
func (*Store) RefLog ¶ added in v0.0.21
RefLog implements agentsession.RefStore.
func (*Store) Release ¶
Release forgets an open session so the next Open reloads it from the database, and drops the hold when the store has one. On a read-only store it is how a reader sees what another process has appended since it opened the session.
The store holds a session once, however many callers opened it, so Release lets it go for all of them: a writer still appending through this store has the session loaded again at its next append, or is refused with ErrSessionLocked if another process took it meanwhile, and the Session it was handed no longer follows the store.
func (*Store) ResolveRef ¶ added in v0.0.21
ResolveRef implements agentsession.RefStore.