Documentation / guides
Session catalog
List and inspect stored Session identities and metadata.
The catalog is a replay-free KV projection for session pickers and status readers. It is derived from the event ledger and can be rebuilt at any time. The appender updates it after a successful durable event append; readers never need to open a ledger cursor for the common list or one-session lookup.
Catalog contract
The exact public types and methods are:
type CatalogOption func(*catalogOptions)
func WithCatalogClock(CatalogClock) CatalogOption
func WithCatalogLogger(CatalogLogger) CatalogOption
func WithCatalogReplayer(EventReplayerOpener) CatalogOption
type CatalogClock func() time.Time
type CatalogLogger interface {
CatalogUpdateFailed(error)
}
type EventReplayerOpener interface {
OpenInternalEventReplayer(uuid.UUID, ReplayRequest) (journal.EventReplayer, error)
}
func (s *Store) OpenCatalog(...CatalogOption) *Catalog
func (c *Catalog) UpdateOnEvent(context.Context, event.Event, uint64) error
func (c *Catalog) ListSessions(context.Context) ([]SessionMeta, error)
func (c *Catalog) ReadMeta(context.Context, uuid.UUID) (SessionMeta, bool, error)
func (c *Catalog) RepairCatalog(context.Context, uuid.UUID) (SessionMeta, error)
UpdateOnEvent always returns nil by contract. A KV failure is sent to the
optional logger and repaired later. ListSessions and ReadMeta surface typed
read/decode errors. RepairCatalog surfaces failures because repair was an
explicit request.
Session meta fields
The exact JSON-backed projection is:
type SessionMeta struct {
SessionID uuid.UUID
Title string
CreatedAt time.Time
LastActiveAt time.Time
Status SessionStatus
AgentKind string
LoopCount int
ConfigFingerprint event.ConfigFingerprint
State SessionState
LastJournalSeq uint64
ActiveTurnID uuid.UUID
WaitingGateID uuid.UUID
LastCheckpoint CheckpointSummary
CurrentWorkspace WorkspacePointer
Loops []LoopUsageMeta
Hustles []HustleUsageAggregate
// LastTurn and LastStep are codec-safe private eventSummary pointers.
}
The source adds JSON tags and private LastTurn/LastStep summaries. Their
wire form stores the journal sequence plus marshaled event bytes, not a bare
interface value.
SessionStatus has StatusActive and StatusStopped. SessionState has
StateRunning, StateWaitingOnGate, StateIdle, StateFailed,
StateInterrupted, and StateStopped. StateStopped wins over every later
projection and survives on disk.
The bounded loop projection is:
type LoopUsageMeta struct {
LoopID uuid.UUID
Runtime event.ModelRuntime
RuntimeSeq uint64
RuntimeValueSeq uint64
CumulativeUsage content.Usage
ContextSeq uint64
ContextValueSeq uint64
CurrentContext event.ContextMeasurement
}
type WorkspacePointer struct {
Ref workspacestore.Ref
EventID uuid.UUID
Seq uint64
Source WorkspacePointerSource
}
type CheckpointSummary struct {
Ref workspacestore.Ref
EventID uuid.UUID
Seq uint64
Consistency event.SnapshotConsistency
}
Loops is sorted by loop ID and folds authoritative StepDone usage exactly
once. Context and runtime watermarks prevent delayed lifecycle events from
regressing the projection.
Projection and CAS
applyEvent maps only catalog-relevant events. SessionStarted seeds identity
and the primary loop; TurnStarted, GateOpened, gate resolution, turn
terminal events, StepDone, RestoreDone, LoopStarted, and SessionStopped
update bounded fields. Every changed event advances LastJournalSeq by max,
never by blind assignment.
The online path is a read-modify-write under storage KV revision CAS. It retries
conflicts up to eight times; exhaustion returns *CatalogConflictError to
repair logic, while the best-effort appender path logs and swallows it.
%%{init: {"theme":"dark"}}%%
sequenceDiagram
participant A as Event appender
participant C as Catalog
participant K as KV
participant L as Ledger
A->>L: durable append event
A->>C: UpdateOnEvent(event, seq)
C->>K: Get sessions/UUID, revision
C->>C: applyEvent(meta, event, seq)
C->>K: Put(expected revision)
alt CAS conflict
C->>K: re-read and retry, up to 8
end
Note over L,K: KV is a cache; RepairCatalog folds L when needed
Read and repair
ListSessions reads keys then values from KV and returns deterministic session
ID order. A key deleted between those operations is skipped. ReadMeta makes
one KV load and returns (zero, false, nil) for absence.
RepairCatalog opens privileged event replay, requires a SessionStarted,
folds from the beginning, and writes under CAS. If a newer cached
LastJournalSeq appears while scanning, it rescans rather than overwriting a
newer projection. Missing starts return *EmptySessionError; corrupt cache
data is replaceable by repair.
Catalog example
catalog := store.OpenCatalog()
meta, found, err := catalog.ReadMeta(ctx, id)
if err != nil {
var read *sessionstore.CatalogReadError
if errors.As(err, &read) {
meta, err = catalog.RepairCatalog(ctx, id)
found = err == nil
}
}
if err != nil {
return err
}
if found {
fmt.Printf("%s %s %s\\n", meta.SessionID, meta.State, meta.Title)
}
Do not use SessionMeta as authorization or restore truth. It contains bounded
derived summaries; the journal contains the authoritative event and command
history.