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 ¶
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.
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 ¶
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 ¶
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 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.