Help us shape the future of Go! Take the 2026 Go Developer Survey

saga

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 25, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Persistence for the saga Store - same reasoning as internal/approval's: a proxy restart mid-run must not silently lose the ability to compensate steps that already executed. Best-effort writes; a storage hiccup is logged, never blocks or fails the in-memory operation.

Postgres-backed persistence for multi-instance/HA deployments - same reasoning as internal/approval/postgres.go: the in-memory map + mutex is correct for one process, but a saga step's Seq assignment (len(s.runs[runID]) today) would collide across two instances appending to the same run concurrently. Postgres mode locks a run's existing rows before assigning the next Seq, serializing that assignment across instances the same way the in-process mutex does within one. MarkCompensated/MarkCompensationFailed need no such locking - they're idempotent single-row UPDATEs, and two instances marking the same step compensated concurrently land on the identical end state.

Package saga records each run's execution trace so it can be walked back. It is simpler than a "dynamic DAG" once one fact is taken seriously: an agent's tool-calling loop is sequential from this proxy's vantage point - one call, one result, one next decision. There is no concurrent branching to reconcile here. So the "graph" a run's steps need is just an ordered list, appended to as calls actually execute, with no workflow pre-declared anywhere. That's the literal meaning of "built from the execution trace, not a static declaration" - branching or retrying earlier in an agent's reasoning never has to be modeled, because only what actually executed ever lands in the trace.

Only Class A steps are ever compensated - that's what Class A means. Class B/C steps still get recorded for a human's visibility into the full run, they're just never walked back.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Step

type Step struct {
	RunID              string
	Seq                int
	Tool               string
	ReversibilityClass string
	Resource           string
	Args               map[string]any
	PreImage           json.RawMessage
	DelegatedUser      string
	DelegatedRole      string
	AgentID            string
	ExecutedAt         time.Time

	Compensated        bool
	CompensationFailed bool
	CompensationNote   string
}

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store holds each tracked run's ordered step trace. Same shape as internal/approval.Store: in-memory by default (tests), optionally SQLite-backed (NewPersistentStore) so a proxy restart mid-run doesn't lose the ability to compensate it, or Postgres-backed (NewPostgresStore, postgres.go) for multi-instance/HA deployments - see that file's doc comment for why Postgres mode is a genuinely different concurrency model, not just a different file format.

func NewPersistentStore

func NewPersistentStore(dbPath string) (*Store, error)

func NewPostgresStore

func NewPostgresStore(dsn string) (*Store, error)

NewPostgresStore opens (creating the schema if needed) a Postgres-backed Store safe for concurrent use by multiple proxy processes against the same database. Reuses the same "pgx" database/sql driver as internal/approval's Postgres backend and cmd/rinwall's DB dynamic-secrets path - one driver, blank-imported wherever it's used directly.

func NewStore

func NewStore() *Store

func (*Store) Close

func (s *Store) Close() error

Close releases the underlying DB handle, if any (NewStore's in-memory Store has none, and Close is a no-op for it).

func (*Store) HasCompensableSteps

func (s *Store) HasCompensableSteps(runID string) bool

HasCompensableSteps reports whether runID has any executed, not-yet- compensated Class A step - the gate before bothering to trigger a compensation walk at all.

func (*Store) MarkCompensated

func (s *Store) MarkCompensated(runID string, seq int)

func (*Store) MarkCompensationFailed

func (s *Store) MarkCompensationFailed(runID string, seq int, note string)

func (*Store) RecordStep

func (s *Store) RecordStep(step *Step)

RecordStep appends a step to its run's trace - always append-only; the sequence number is assigned here so callers never have to coordinate it.

func (*Store) StepsFor

func (s *Store) StepsFor(runID string) []*Step

StepsFor returns a run's trace in EXECUTION order (oldest first). Callers that want to compensate walk this slice backwards themselves - Store has no opinion on compensation ordering, only on what happened and when.

Jump to

Keyboard shortcuts

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