Documentation
¶
Overview ¶
Package eventlog is the durable, append-only audit log that backs agent.Agent's session.Service. Each event the ADK runner appends to a session is persisted to the underlying database (SQLite, MySQL, or Postgres via GORM) and assigned a monotonic seq number. Subscribers can replay history with Since(fromSeq) or live-tail with Watch(fromSeq).
The package layers on top of ADK's session/database service: ADK owns the events / sessions / state tables, and we add a thin agent_eventlog overlay table whose rows reference ADK's events by id and add the seq column. Two GORM connections (ADK's and ours) share the same database file/DSN — atomic-across-tables writes are not provided in v1; the AppendEvent path writes ADK first, then the overlay, and surfaces overlay-write errors so callers can retry (event_id is unique-indexed for safe idempotency).
See docs/eventlog-plan.md and docs/eventlog-decisions.md for the design rationale and milestone breakdown.
Index ¶
- Variables
- type Entry
- type Handle
- type Option
- type QueryOption
- func ForSession(appName, userID, sessionID string) QueryOption
- func WithAuthor(name string) QueryOption
- func WithAuthorSuffix(suffix string) QueryOption
- func WithBranchPrefix(prefix string) QueryOption
- func WithLimit(n int) QueryOption
- func WithSessionTree(appName, userID, parentSessionID string) QueryOption
- type SessionLock
- type Stream
Constants ¶
This section is empty.
Variables ¶
var ErrClosed = errors.New("eventlog: stream is closed")
ErrClosed is returned by Stream methods invoked after Close.
var ErrSessionLocked = errors.New("eventlog: session is locked by another process")
ErrSessionLocked is returned by AcquireLock when another live process already holds the lease. The error message includes the holder identifier so operators can diagnose contention.
Functions ¶
This section is empty.
Types ¶
type Entry ¶
Entry is one row from the event log: the assigned seq plus the underlying ADK session.Event (loaded via the paired session.Service).
type Handle ¶
type Handle struct {
// Stream is the seq + replay + watch primitive.
Stream Stream
// Service is the session.Service backed by the same database.
// Pass to agent.WithSessionService (or use the
// agent.WithEventLog convenience that does both at once).
Service session.Service
// contains filtered or unexported fields
}
Handle bundles the Stream with the session.Service that writes to the same database. agent.WithEventLog(handle) wires both into an agent.Agent in one call.
func Open ¶
Open constructs a Handle backed by the supplied GORM dialector. Pass any standard dialector (sqlite.Open, postgres.Open, mysql.Open).
Open does several things:
- Constructs ADK's database.SessionService against the dialector and runs its AutoMigrate so the events / sessions / state tables exist.
- Opens a second GORM connection for our overlay table and AutoMigrates agent_eventlog.
- For SQLite (detected via the dialector's Name()), enables WAL journal mode so concurrent readers can run alongside the writer. Disable with WithSkipWAL.
- Wraps the ADK service so AppendEvent writes to both layers.
func (*Handle) AcquireLock ¶
AcquireLock takes an exclusive lease on (app, user, session) for the lifetime of the returned *SessionLock. Returns ErrSessionLocked if another process holds a fresh lease (heartbeat within staleAfter); steals the lease if the existing holder's heartbeat is older than staleAfter (indicating a crashed process).
The lease is heartbeated automatically until SessionLock.Release is called; Release is idempotent and safe to defer.
type Option ¶
type Option func(*openOpts)
Option configures Open.
func WithGORMConfig ¶
WithGORMConfig overrides the gorm.Config used for the overlay connection. Useful for silencing the default logger in tests (gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}).
func WithSkipWAL ¶
func WithSkipWAL() Option
WithSkipWAL disables the automatic PRAGMA journal_mode=WAL set on SQLite databases at Open time. WAL is on by default because it permits concurrent readers alongside a writer; turn it off for in-memory databases or read-only setups where WAL adds no value.
func WithWatchInterval ¶
WithWatchInterval sets the polling interval Watch uses to check for new rows. Default is 200ms. Smaller values reduce subscriber latency at the cost of database load; larger values do the opposite.
type QueryOption ¶
type QueryOption func(*queryOpts)
QueryOption filters Since/Watch results.
func ForSession ¶
func ForSession(appName, userID, sessionID string) QueryOption
ForSession restricts results to one session triple. Without it, queries scan across every session in the database — useful for audit dashboards, dangerous for high-volume reads.
func WithAuthor ¶
func WithAuthor(name string) QueryOption
WithAuthor matches events emitted by a specific author. The autonomous driver uses Author="<binary>/autonomous" for checkpoint events; consumer-supplied authors work the same way.
func WithAuthorSuffix ¶
func WithAuthorSuffix(suffix string) QueryOption
WithAuthorSuffix matches events whose Author ends with the supplied suffix. Used by ResumeAutonomous to find checkpoint events regardless of which binary emitted them — checkpoints land with Author="<binary>/autonomous", so suffix "/autonomous" matches checkpoints from any core-agent-family process. Empty suffix is a no-op (matches everything).
func WithBranchPrefix ¶
func WithBranchPrefix(prefix string) QueryOption
WithBranchPrefix matches events whose Branch field begins with prefix. Use to scope queries to a subagent subtree once Phase 4 of the eventlog plan ships subagent runners that set Branch.
func WithLimit ¶
func WithLimit(n int) QueryOption
WithLimit caps the number of entries returned. Zero or negative is treated as no limit.
func WithSessionTree ¶
func WithSessionTree(appName, userID, parentSessionID string) QueryOption
WithSessionTree restricts results to the parent session ID and any derived sub-session IDs. The subagent runner names its session "<parent>:sub:<branch>" by convention; this option's underlying SQL matches the parent + every "<parent>:sub:%" descendant in one query so an audit can pull the whole tree without a follow-up join.
When set, takes precedence over the (App, User, Session) triple from ForSession — the two are mutually exclusive in practice because WithSessionTree implies the (app, user) pair already. Mutually composable with the other QueryOptions (WithBranchPrefix, WithAuthor, WithAuthorSuffix, WithLimit).
type SessionLock ¶
type SessionLock struct {
// contains filtered or unexported fields
}
SessionLock is the lease returned by Handle.AcquireLock. It runs a background goroutine that refreshes heartbeat_at every heartbeatInterval until Release is called. Safe to call Release multiple times.
func (*SessionLock) Holder ¶
func (l *SessionLock) Holder() string
Holder returns the identifier we registered when acquiring the lock. Useful for diagnostics + for tests that need to assert the row content.
func (*SessionLock) Release ¶
func (l *SessionLock) Release() error
Release ends the lease and stops the heartbeat goroutine. Idempotent; safe to defer.
type Stream ¶
type Stream interface {
// Append writes ev to the log under sess. Returns the assigned
// seq number. The event itself is also expected to be persisted
// via the paired session.Service.AppendEvent — Stream.Append
// only writes the overlay row that carries the seq.
//
// Most callers don't invoke this directly; agent.Run drives the
// session.Service which in turn calls Append internally.
Append(ctx context.Context, sess session.Session, ev *session.Event) (seq int64, err error)
// Since returns events with seq > fromSeq, in seq order. Bounded
// by current end-of-log; returns when caught up. Apply filters
// via QueryOption (ForSession, WithBranchPrefix, WithAuthor,
// WithLimit).
Since(ctx context.Context, fromSeq int64, opts ...QueryOption) iter.Seq2[Entry, error]
// Watch returns events with seq > fromSeq, in seq order, blocking
// for new events as they're appended. Cancel ctx to stop. Same
// QueryOptions as Since.
//
// The default poll interval is 200ms, configurable via Open's
// WithWatchInterval option.
Watch(ctx context.Context, fromSeq int64, opts ...QueryOption) iter.Seq2[Entry, error]
// Close releases resources held by the Stream (typically the
// underlying gorm.DB connection pool). Safe to call multiple
// times.
Close() error
}
Stream is the append-only event log primitive. Implementations are expected to be safe for concurrent use.