Documentation
¶
Overview ¶
Package state defines the persistence boundary between the open-source agent and any host. This is the host boundary: the agent reaches all durable state - sessions, skills, memory - only through the interfaces here.
The open agent ships a local implementation (in-memory in memory.go; a durable SQLite implementation in storage/sqlite). A commercial host such as an Ion Alpha instance can supply its own Provider backed by a knowledge graph and fleet-wide learning, without this package ever importing the host.
Index ¶
- Constants
- Variables
- func MarshalSnapshot(s Snapshot) ([]byte, error)
- type Envelope
- type MemoryItem
- type MemoryStore
- type Option
- type Provider
- type RecallQuery
- type Scope
- type Session
- type SessionStore
- type Skill
- type SkillStore
- type Snapshot
- type Stamper
- func (s *Stamper) AppendTurn(ses Session, t Turn, nextSeq int64) (Turn, Session, spine.AppendInput, error)
- func (s *Stamper) CreateSession(ses Session) (Session, spine.AppendInput, error)
- func (s *Stamper) DeleteMemory(it MemoryItem) (MemoryItem, spine.AppendInput, error)
- func (s *Stamper) DeleteSession(ses Session) (Session, spine.AppendInput, error)
- func (s *Stamper) DeleteSkill(sk Skill) (Skill, spine.AppendInput, error)
- func (s *Stamper) InstanceID() string
- func (s *Stamper) UpsertSkill(existing *Skill, sk Skill) (Skill, spine.AppendInput, error)
- func (s *Stamper) WriteMemory(it MemoryItem) (MemoryItem, spine.AppendInput, error)
- type Turn
Constants ¶
const ( EvSessionCreated = "session.created" EvTurnAppended = "session.turn_appended" EvSessionDeleted = "session.deleted" EvSkillUpserted = "skill.upserted" EvSkillDeleted = "skill.deleted" EvMemoryWritten = "memory.written" EvMemoryDeleted = "memory.deleted" )
State event types: the vocabulary of the state stream. Each event is the post-image of the affected record(s): the Stamper computes the canonical record (IDs, Seq, HLC, version, timestamps all assigned) and it is written as the event payload, so replaying the events in Seq order reproduces identical state without re-running any clock or RNG. Exported so durable backends project the same vocabulary rather than redeclaring the strings.
const StateStream = "state"
StateStream is the spine stream every state mutation is recorded on. A single ordered stream for sessions, skills, and memory means a Replay folds one log to reconstruct the whole provider. It is exported so a host can observe or audit the state stream directly.
Variables ¶
var ErrConflict = errors.New("state: version conflict")
ErrConflict is returned by a write when optimistic concurrency fails: the caller passed a non-zero SyncVersion that no longer matches the stored record (someone else wrote in between). Re-read and retry.
var ErrNotFound = errors.New("state: not found")
ErrNotFound is returned by stores when a requested record does not exist.
Functions ¶
func MarshalSnapshot ¶
MarshalSnapshot serializes a state projection into the payload a Replay or Rebuild restores from. The collections are sorted so the payload is deterministic for a given projection: sessions, skills, and items by id, turns by session then seq.
Types ¶
type Envelope ¶
Envelope is the sync/concurrency metadata carried by every persisted record, embedded into Session, Turn, Skill, and MemoryItem. It is the shared sync envelope (see the envelope package for the fields and the stamping rules): state records and resources carry the identical five fields under identical rules, which is what keeps fleet merge one discipline instead of two.
SyncVersion powers optimistic concurrency: on an update, pass the version you read and the write fails with ErrConflict if the stored version has moved (a zero SyncVersion means "unconditional").
type MemoryItem ¶
type MemoryItem struct {
ID string
Kind string // e.g. "fact", "preference", "observation"
Content string
Scope Scope
Source string // provenance: where this memory came from
CreatedAt time.Time
Envelope
}
MemoryItem is a durable fact the agent has learned, attributable to its source for provenance and rollback.
func DecodeMemoryItem ¶
func DecodeMemoryItem(payload map[string]any) (MemoryItem, error)
DecodeMemoryItem extracts the MemoryItem post-image from a state event payload.
type MemoryStore ¶
type MemoryStore interface {
// Write persists a memory item, assigning an ID if one is not set.
Write(ctx context.Context, m MemoryItem) (MemoryItem, error)
// Recall returns memory matching the query.
Recall(ctx context.Context, q RecallQuery) ([]MemoryItem, error)
// Delete tombstones a memory item by ID (soft delete), or returns
// ErrNotFound.
Delete(ctx context.Context, id string) error
}
MemoryStore persists and recalls memory. The durable implementation combines lexical (FTS5) and vector (chromem-go) recall; the in-memory implementation does a case-insensitive substring scan, most-recent first.
type Option ¶
type Option func(*memProvider)
Option configures the in-memory Provider.
func WithClock ¶
WithClock sets the time source for record timestamps (default clock.System), so tests and deterministic replay can supply a clock.Manual. The same clock stamps the event log when one is not injected separately.
func WithEventLog ¶
WithEventLog backs the provider with a specific spine.Log instead of a private in-memory one. Inject a shared log to observe, audit, or Replay the state stream; pass the same log to Replay to reconstruct the provider from it.
func WithIDGenerator ¶
WithIDGenerator sets the source of record IDs (default: a generator on the provider's clock with crypto/rand entropy). Supply a generator seeded with a deterministic clock and entropy so a re-run with the same seeds produces the exact same IDs - the basis of deterministic replay.
func WithInstanceID ¶
WithInstanceID sets the origin/last-writer instance stamped onto records this provider creates (default "local"). The agent passes its own instance identity here so fleet/P2P merge can attribute records.
func WithSnapshotCodec ¶
func WithSnapshotCodec(c spine.SnapshotCodec) Option
WithSnapshotCodec makes the provider's snapshots verified: Snapshot seals the projection payload through the codec before saving it, and Replay opens (verifies) a stored snapshot through it before restoring. A snapshot that fails to open is skipped and the stream folds from the start, so an unsigned or tampered snapshot is never restored. With no codec the payload is stored as-is.
func WithSnapshotEvery ¶
WithSnapshotEvery checkpoints the provider automatically: after every k successful mutations it writes a snapshot, so a Replay folds at most k events past the last checkpoint. The snapshot is best effort, so a failure never fails the write. Zero or negative disables automatic snapshots (the default); Snapshot can still be called explicitly.
type Provider ¶
type Provider interface {
// Name identifies the backend for diagnostics, e.g. "memory", "sqlite".
Name() string
// Sessions returns the durable conversation store.
Sessions() SessionStore
// Skills returns the scoped, searchable skill store.
Skills() SkillStore
// Memory returns the durable memory store.
Memory() MemoryStore
// Close releases any resources held by the provider.
Close() error
}
Provider is the agent's durable backend: the single interface a host implements to back the agent with its own storage. The agent never depends on a concrete store, only on this Provider and the stores it returns.
func NewMemory ¶
NewMemory returns an empty in-memory Provider so the agent runs with zero setup. It is safe for concurrent use and intended as the standalone default and for tests. Every mutation is recorded on a spine.Log and projected, so the provider's state is always a fold of its log (see Replay).
func Replay ¶
Replay reconstructs an in-memory Provider purely by folding a log's "state" stream: the running proof that state is a projection of the spine. The returned Provider is backed by the same log, with its projection caught up to the last recorded event, so subsequent writes continue the stream.
type RecallQuery ¶
RecallQuery selects memory for prefetch into context. Query is matched lexically (and, in vector-capable backends, semantically); Scope narrows the search; Limit caps results (<= 0 means no cap).
type Scope ¶
Scope locates a resource on the instance/project/workspace axis, so skills and memory can be partitioned and resolved most-specific-first. The zero Scope is the global (instance) scope. Scope is comparable.
type Session ¶
type Session struct {
ID string
Title string
Model string
CreatedAt time.Time
UpdatedAt time.Time
Envelope
}
Session is a durable, resumable conversation. Sessions survive process restarts so a crashed or disconnected run can be picked back up - the agent's answer to message loss in ephemeral, file-based agents.
type SessionStore ¶
type SessionStore interface {
// Create persists a new session, assigning an ID if one is not set.
Create(ctx context.Context, s Session) (Session, error)
// Get returns the session by ID, or ErrNotFound.
Get(ctx context.Context, id string) (Session, error)
// List returns all sessions, oldest first.
List(ctx context.Context) ([]Session, error)
// AppendTurn appends a turn to its session, assigning ID and Seq, and bumps
// the session's SyncVersion. It returns ErrNotFound if the session does not
// exist.
AppendTurn(ctx context.Context, t Turn) (Turn, error)
// Turns returns a session's transcript in Seq order.
Turns(ctx context.Context, sessionID string) ([]Turn, error)
// Delete tombstones a session by ID (soft delete), or returns ErrNotFound.
Delete(ctx context.Context, id string) error
}
SessionStore persists conversations and their transcripts. Turns are append-only; resuming a session means reading its turns back in Seq order.
type Skill ¶
type Skill struct {
ID string
Slug string
Name string
Body string
Tags []string
Scope Scope
// Uses and Wins are outcome evidence: how many runs recalled this skill, and
// how many of those runs then succeeded. They let a skill be ranked and retired
// by how well it has actually performed, not by recency alone.
Uses int
Wins int
// Check is an optional shell command that verifies the skill still works, kept
// so the skill can be re-graded later (re-run as the environment changes).
Check string
Version int
CreatedAt time.Time
UpdatedAt time.Time
Envelope
}
Skill is a reusable, versioned unit of learned procedure. Slug is unique within a Scope; Body is the skill content. Version is the content revision (for provenance/rollback), distinct from Envelope.SyncVersion (the concurrency/sync token).
type SkillStore ¶
type SkillStore interface {
// Upsert creates or updates a skill keyed by (Scope, Slug). On update the
// content Version is incremented, CreatedAt and OriginInstanceID preserved,
// and SyncVersion bumped. Optimistic concurrency is opt-in: a non-zero
// SyncVersion on the passed skill must match the stored record, else
// ErrConflict; a zero SyncVersion writes unconditionally.
Upsert(ctx context.Context, sk Skill) (Skill, error)
// Get returns a skill by ID or slug, or ErrNotFound.
Get(ctx context.Context, idOrSlug string) (Skill, error)
// List returns the skills in a scope, ordered by slug.
List(ctx context.Context, scope Scope) ([]Skill, error)
// Search returns skills matching query, ordered by slug, capped at limit
// (limit <= 0 means no cap).
Search(ctx context.Context, query string, limit int) ([]Skill, error)
// Delete tombstones a skill by ID or slug (soft delete), or returns
// ErrNotFound.
Delete(ctx context.Context, idOrSlug string) error
}
SkillStore persists scoped skills and searches them. The durable implementation backs Search with full-text search (SQLite FTS5); the in-memory implementation does a case-insensitive substring scan.
type Snapshot ¶
type Snapshot struct {
LastSeq int64 `json:"lastSeq"`
Sessions []Session `json:"sessions"`
Turns []Turn `json:"turns"`
Skills []Skill `json:"skills"`
Items []MemoryItem `json:"items"`
SlugToID map[string]string `json:"slugToID,omitempty"`
}
Snapshot is a materialized projection of the state stream up to LastSeq: every session, its turns, every skill (live and tombstoned), every memory item, and the derived skill-slug index, enough to restore the read model without folding the stream from the start. It is the state analogue of the resource snapshot, opaque to the spine and sealed by the same codec when one is configured. The slug index is carried so an in-memory restore is exact; a table-backed store rebuilds it from its own rows and leaves the field nil.
func UnmarshalSnapshot ¶
UnmarshalSnapshot decodes a state snapshot payload, the inverse of MarshalSnapshot, so any backend restores from the one shared format.
type Stamper ¶
type Stamper struct {
// contains filtered or unexported fields
}
Stamper computes the canonical post-image of a state mutation: it assigns the record ID, timestamps, the hybrid-logical clock, the content and sync versions, and the sync envelope, enforces optimistic concurrency (CAS), and builds the spine event to append. Every backend routes its writes through one Stamper, so the envelope/version/tombstone rules are defined exactly once and the in-memory and durable backends cannot drift (the duplication that previously lived in both state/memory.go and the SQLite adapter as Go and as SQL).
A Stamper does no persistence and reads no store: the caller supplies the existing record (looked up under its own lock or transaction) and persists the returned record and event. This keeps the rules backend-agnostic and pure, so the same stamp produces the same record regardless of which backend stores it.
func NewStamper ¶
NewStamper builds a Stamper from a backend's instance identity and its injected deterministic primitives, so a re-run with the same seeds produces identical records and IDs (the basis of deterministic replay).
func (*Stamper) AppendTurn ¶
func (s *Stamper) AppendTurn(ses Session, t Turn, nextSeq int64) (Turn, Session, spine.AppendInput, error)
AppendTurn stamps a turn at nextSeq and the envelope bump it induces on its session, returning both plus the single event that records the pair. The caller supplies the live session (so its envelope advances under the same HLC) and the next sequence number from its own store.
func (*Stamper) CreateSession ¶
CreateSession stamps a new session and returns it with the event to append.
func (*Stamper) DeleteMemory ¶
func (s *Stamper) DeleteMemory(it MemoryItem) (MemoryItem, spine.AppendInput, error)
DeleteMemory tombstones the given live memory item and returns the event.
func (*Stamper) DeleteSession ¶
DeleteSession tombstones the given live session and returns the event.
func (*Stamper) DeleteSkill ¶
DeleteSkill tombstones the given live skill (bumping the content version too, so a delete is itself a revision) and returns the event.
func (*Stamper) InstanceID ¶
InstanceID is the instance this Stamper attributes writes to.
func (*Stamper) UpsertSkill ¶
UpsertSkill stamps a create or an update keyed by (Scope, Slug). existing is the stored record for that key (tombstones included, so an upsert over a tombstone resurrects it) or nil if none. Optimistic concurrency is opt-in: a non-zero SyncVersion on the input must match the stored one, else ErrConflict; a non-zero SyncVersion with no existing record is also a conflict (it expected one).
func (*Stamper) WriteMemory ¶
func (s *Stamper) WriteMemory(it MemoryItem) (MemoryItem, spine.AppendInput, error)
WriteMemory stamps a new memory item and returns the event.