eventlog

package
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 16, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
var ErrClosed = errors.New("eventlog: stream is closed")

ErrClosed is returned by Stream methods invoked after Close.

View Source
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

type Entry struct {
	Seq   int64
	Event *session.Event
}

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

func Open(ctx context.Context, dialector gorm.Dialector, opts ...Option) (*Handle, error)

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

func (h *Handle) AcquireLock(ctx context.Context, app, user, session string) (*SessionLock, error)

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.

func (*Handle) Close

func (h *Handle) Close() error

Close releases all resources held by the Handle (Stream + the underlying database connection). Safe to call multiple times.

type Option

type Option func(*openOpts)

Option configures Open.

func WithGORMConfig

func WithGORMConfig(c *gorm.Config) Option

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

func WithWatchInterval(d time.Duration) Option

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL