threadstore

package
v1.1.2 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

Documentation

Overview

Package threadstore defines the storage contract behind stateful Threads (see docs/api-reference.md §13).

A Store persists the driver resume checkpoints that let a Thread continue a conversation across runs and processes. It resolves and atomically finalizes records, while a lease trio guards concurrent use. The host owns one opaque thread key; multi-tenant hosts can use their own collision-free encoding when several dimensions must share that key.

A Store holds resume tokens, compatibility fingerprints, and lease coordination. It is not a chat-history store, an HITL pending queue, and not "the conversation a user sees in a chat UI" — hosts that need those compose their own recording on top (see hosttools/sessionrecorder).

Dependency rule: this package may import the driver SPI package only — never the root package or the internal engine.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrBusy matches AcquireLease failures while another owner holds an
	// unexpired lease on the target.
	ErrBusy = errors.New("threadstore: target busy")
	// ErrLeaseLost matches RenewLease/Finalize failures when the caller's
	// lease token is no longer current.
	ErrLeaseLost = errors.New("threadstore: lease lost")
	// ErrAlreadyExists matches a conditional Finalize that found an active
	// mapping where the caller required the thread key to be unused.
	ErrAlreadyExists = errors.New("threadstore: thread already exists")
)

Base sentinels for errors.Is matching. The struct errors below unwrap to them and carry the concrete target.

Functions

This section is empty.

Types

type AlreadyExistsError

type AlreadyExistsError struct {
	Key string
}

AlreadyExistsError is returned by a conditional Finalize when Key already has an active mapping. Unwrap returns ErrAlreadyExists.

func (*AlreadyExistsError) Error

func (e *AlreadyExistsError) Error() string

Error reports the conflicting host thread key when present.

func (*AlreadyExistsError) ThreadAlreadyExists

func (e *AlreadyExistsError) ThreadAlreadyExists() bool

ThreadAlreadyExists marks this as the store-neutral conditional-finalize conflict understood by the internal coordinator. The method deliberately carries no data and keeps threadstore independent of internal/engine.

func (*AlreadyExistsError) Unwrap

func (e *AlreadyExistsError) Unwrap() error

Unwrap returns ErrAlreadyExists so errors.Is(err, ErrAlreadyExists) holds.

type BusyError

type BusyError struct {
	Target string
}

BusyError is returned by AcquireLease when the target is exclusively held by another owner. Unwrap returns ErrBusy.

func (*BusyError) Error

func (e *BusyError) Error() string

Error reports the busy target when present.

func (*BusyError) Unwrap

func (e *BusyError) Unwrap() error

Unwrap returns ErrBusy so errors.Is(err, ErrBusy) holds.

type FinalizeRequest

type FinalizeRequest struct {
	Record     Record
	PreviousID string
	// Key is the thread key whose active mapping is rebound when
	// RebindActive is set.
	Key        string
	HeldLeases []Lease
	// ArchiveOld archives the PreviousID record (resume-fallback paths keep
	// the old conversation addressable for audit).
	ArchiveOld bool
	// RebindActive points the Key's active mapping at Record.ID.
	RebindActive bool
	// RequireKeyAbsent makes Finalize fail atomically with ErrAlreadyExists
	// when Key already has an active mapping. Fork uses this compare-and-set
	// guard in addition to its key lease so a stale or non-cooperating writer
	// cannot create two active children for the same host key.
	RequireKeyAbsent bool
}

FinalizeRequest tells a Store how to persist the post-run thread state: save the new record, optionally archive the previous one, and rebind the key's active mapping — atomically relative to the store's backend, after validating every held lease. When RequireKeyAbsent is set, checking the key precondition and applying all mutations are one atomic operation.

type Lease

type Lease struct {
	Target string
	Owner  string
	Token  string
}

Lease is the concurrent-use guard returned by Store.AcquireLease. Stores must validate Owner+Token during Finalize, RenewLease, and ReleaseLease so an expired-and-reacquired lease can never finalize stale state.

type LeaseLostError

type LeaseLostError struct {
	Target string
}

LeaseLostError is returned by RenewLease/Finalize when the caller no longer owns the lease (expired, released, or reacquired by someone else). Unwrap returns ErrLeaseLost.

func (*LeaseLostError) Error

func (e *LeaseLostError) Error() string

Error reports the lost lease target when present.

func (*LeaseLostError) Unwrap

func (e *LeaseLostError) Unwrap() error

Unwrap returns ErrLeaseLost so errors.Is(err, ErrLeaseLost) holds.

type Query

type Query struct {
	ID              string
	Key             string
	IncludeArchived bool
}

Query is the lookup shape passed to Store.Resolve. Exactly one of ID or Key is set. Archived records are only returned when IncludeArchived is true.

type Record

type Record struct {
	// ID is the SDK-assigned internal session identifier. Consumers use the
	// thread key and run IDs; stores index by ID and keep it stable for the
	// record's lifetime.
	ID string
	// Key is the host's thread key — the stable business handle. Multiple
	// records may share a Key over time when a resume fallback archives the
	// old one; at most one of them is StatusActive. Fork requires a previously
	// unused target key and never archives its parent.
	Key string
	// Status is the lifecycle state (active / archived).
	Status Status
	// DriverType records which driver produced the checkpoint.
	DriverType string
	// Agent is the caller identity captured at persist time.
	Agent driver.AgentIdentity
	// Fingerprint is the invocation fingerprint captured at persist time.
	Fingerprint string
	// CompatibilityFingerprint is the guard compared on resume; a mismatch
	// rejects the resume instead of contaminating the conversation.
	CompatibilityFingerprint string
	// SessionCodec is the stable name of the driver codec that normalized
	// State. Fork and resume coordination use it to reject checkpoint formats
	// that the current driver cannot safely interpret.
	SessionCodec string
	// State is the driver-owned resume checkpoint.
	State *driver.SessionState
	// CreatedAt/UpdatedAt are storage timestamps (UTC).
	CreatedAt time.Time
	UpdatedAt time.Time
}

Record is the durable per-conversation record a Store persists. State is driver-owned checkpoint data; the fingerprint fields are used to reject unsafe resumes when important context (identity, model, workspace, instructions, ...) changed between runs.

type Status

type Status string

Status is the storage lifecycle state of a Record.

const (
	// StatusActive marks the current record for a thread key.
	StatusActive Status = "active"
	// StatusArchived marks a previous record retained for audit/forking.
	StatusArchived Status = "archived"
)

type Store

type Store interface {
	Resolve(ctx context.Context, q Query) (*Record, error)
	Finalize(ctx context.Context, req FinalizeRequest) error
	AcquireLease(ctx context.Context, target, owner string, ttl time.Duration) (Lease, error)
	RenewLease(ctx context.Context, lease Lease, ttl time.Duration) error
	ReleaseLease(ctx context.Context, lease Lease) error
}

Store persists Thread state for resume-capable Drivers:

  • Resolve: look up by internal ID or by thread key. A missing record is (nil, nil), not an error. Archived records require IncludeArchived.
  • Finalize: validate every held lease (owner+token, unexpired), then atomically save/archive/rebind.
  • AcquireLease / RenewLease / ReleaseLease: exclusive-use coordination. Acquire fails with a BusyError while another owner holds an unexpired lease on target; acquiring an expired or self-owned lease succeeds. Context and backend failures retain their original errors.Is identity and must not be reported as BusyError. Renew and Finalize fail with a LeaseLostError when the caller no longer owns the matching token. Release is idempotent and ignores lost/stale leases.

Jump to

Keyboard shortcuts

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