Documentation
¶
Overview ¶
Package history defines provider-neutral conversation history contracts and model middleware.
Reader, Writer, Clearer, and Store use core/chat protocol values directly. WindowStore is an explicit read-side retention decorator. It merges system messages and retains only complete user-led turns, so assistant Tool calls, Tool results, reasoning, and final text cannot be split at the read boundary. Optional cross-conversation and replacement capabilities remain separate interfaces. The zero-value-ready reference implementation lives in core/history/inmemory.
Conversation IDs are runtime scope carried with WithConversationID, not serialized request metadata. Middleware binds that scope to model calls.
Writes preserve message order within one call. Conversation listing is an optional capability and returns unique IDs in lexical order. Concurrent writes and writes through distinct Store instances have no common ordering guarantee unless a backend documents one. Writer returns WriteOutcome even on failure; errors never silently mean that no messages were stored. A CommitError identifies history failure after model completion and preserves the attempted messages and write outcome.
The Host owns conversation turn ordering. Concurrent-safe store methods do not isolate the whole Read/model/Write sequence; multi-instance hosts must coordinate that sequence across instances if they require serial turns.
Persistent backends live in independent leaf modules so database drivers do not enter Core:
historystores/postgres/ — PostgreSQL (pgx + JSONB) historystores/redis/ — Redis (RPUSH / LRANGE lists) historystores/mongodb/ — MongoDB (document per message) historystores/cassandra/ — Cassandra (TIMEUUID clustering key) historystores/neo4j/ — Neo4j (node per message) historystores/cosmosdb/ — Azure Cosmos DB (NoSQL API)
Every backend reads and writes only the current core/chat tagged JSON wire.
Example ¶
package main
import (
"fmt"
"github.com/Tangerg/scope/core/history"
)
func main() {
conversationID, err := history.NewConversationID("customer-42")
if err != nil {
panic(err)
}
fmt.Println(conversationID)
}
Output: customer-42
Index ¶
- Variables
- func WithConversationID(ctx context.Context, conversationID ConversationID) context.Context
- type Clearer
- type CommitError
- type ConversationID
- type Lister
- type Middleware
- type ReadWriter
- type Reader
- type Sequence
- type Store
- type WindowStore
- func (w WindowStore) Clear(ctx context.Context, conversationID ConversationID) error
- func (w WindowStore) Read(ctx context.Context, conversationID ConversationID) ([]chat.Message, error)
- func (w WindowStore) Write(ctx context.Context, conversationID ConversationID, messages ...chat.Message) (WriteOutcome, error)
- type WriteOutcome
- type Writer
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrInvalidWindow = errors.New("history: invalid message window") ErrWindowTooSmall = errors.New("history: message window too small") )
var ErrInvalidConversationID = errors.New("history: invalid conversation ID")
var ErrInvalidWriteOutcome = errors.New("history: invalid write outcome")
ErrInvalidWriteOutcome identifies a Writer that returned contradictory facts.
var ErrNilStore = errors.New("history: nil store")
var ErrNilStream = errors.New("history: middleware: nil stream sequence")
ErrNilStream reports a Streamer contract violation before any delta is consumed.
Functions ¶
func WithConversationID ¶
func WithConversationID(ctx context.Context, conversationID ConversationID) context.Context
WithConversationID returns a child context carrying the history partition key for one model call. An empty ID deliberately shadows and disables an ID inherited from a parent context. As with context.WithValue, ctx must not be nil.
Types ¶
type Clearer ¶
type Clearer interface {
// Clear removes the complete conversation and is idempotent when it is
// already absent. Implementations must honor ctx.
Clear(ctx context.Context, conversationID ConversationID) error
}
Clearer removes every message for one conversation.
type CommitError ¶ added in v0.20.0
type CommitError struct {
// contains filtered or unexported fields
}
CommitError means model generation completed but history persistence failed. It owns the exact batch submitted to Write, so recovery need not call the model again. Outcome governs whether a suffix is safe to submit: uncertain writes require reconciliation, and Accepted messages must not be appended again. During streaming this error can follow the terminal model delta.
func (*CommitError) ConversationID ¶ added in v0.20.0
func (c *CommitError) ConversationID() ConversationID
func (*CommitError) Error ¶ added in v0.20.0
func (c *CommitError) Error() string
func (*CommitError) Messages ¶ added in v0.20.0
func (c *CommitError) Messages() []chat.Message
Messages returns an independently owned copy of the complete attempted batch.
func (*CommitError) Outcome ¶ added in v0.20.0
func (c *CommitError) Outcome() WriteOutcome
func (*CommitError) Unwrap ¶ added in v0.20.0
func (c *CommitError) Unwrap() error
type ConversationID ¶
type ConversationID string
ConversationID identifies one history partition. Its zero value is invalid. Use NewConversationID when converting runtime input; string constants may be converted directly when the value is known at compile time.
func ConversationIDFromContext ¶
func ConversationIDFromContext(ctx context.Context) (ConversationID, bool)
ConversationIDFromContext returns the ID carried by ctx. Empty values behave as absent so middleware can transparently skip history for unbound calls.
func NewConversationID ¶
func NewConversationID(value string) (ConversationID, error)
NewConversationID makes the identifier a validated type so a raw, unchecked string cannot reach a store boundary. Product identity stays with the host; this type only guarantees the value is usable as a key.
func (ConversationID) String ¶
func (c ConversationID) String() string
func (ConversationID) Validate ¶
func (c ConversationID) Validate() error
type Lister ¶
type Lister interface {
// Conversations returns detached, unique identifiers in lexical order. An
// empty store yields a non-nil empty slice; concurrent writes may appear or
// not according to the backend's snapshot boundary.
Conversations(ctx context.Context) ([]ConversationID, error)
}
Lister enumerates unique conversation IDs in lexical order. Implementations return a non-nil empty slice when no conversations exist. Concurrent mutations may affect the result.
type Middleware ¶
type Middleware struct {
// contains filtered or unexported fields
}
Middleware replays and persists history around synchronous and streaming chat capabilities. It is immutable after construction and safe for concurrent use when its Store is safe for concurrent use. This does not serialize complete Read/model/Write turns for the same conversation. The Host must serialize those turns across all instances if ordering is required; different conversations may proceed concurrently.
func NewMiddleware ¶
func NewMiddleware(store ReadWriter) (Middleware, error)
NewMiddleware requires a ReadWriter because history is only useful when both halves cross the same boundary: it must read prior turns into the request and write the new ones back. Splitting them would let a caller wire a reader against a different store than the writer.
func (Middleware) Call ¶
func (m Middleware) Call(next chat.Model) chat.Model
Call is a chat.CallMiddleware. The response result is the canonical assistant message persisted to history.
func (Middleware) Stream ¶
func (m Middleware) Stream(next chat.Streamer) chat.Streamer
Stream is a chat.StreamMiddleware. History I/O remains lazy: no read occurs until the returned sequence is iterated. Fresh input and the accumulated assistant response are persisted only after natural, error-free completion.
type ReadWriter ¶
ReadWriter combines the capabilities required by components that replay and append history without owning retention or deletion policy.
type Reader ¶
type Reader interface {
// Read returns a detached snapshot in stored order. Unknown conversations
// yield a non-nil empty slice; implementations honor ctx and never expose
// mutable backing storage.
Read(ctx context.Context, conversationID ConversationID) ([]chat.Message, error)
}
Reader returns the messages to replay for one conversation. Implementations return a non-nil empty slice for an unknown conversation and transfer ownership of returned protocol values to the caller.
type Sequence ¶ added in v0.16.0
type Sequence struct {
// contains filtered or unexported fields
}
Sequence assigns the positions a store writes alongside its messages.
Writer promises that one Write reads back in argument order, and a store that derives positions from the wall clock cannot keep that promise by itself: a clock that steps backward — an NTP correction, a suspended host — would place a later batch before an earlier one, and messages inside a single batch can land on the same instant however fine the clock's resolution. A Sequence hands out a contiguous, strictly increasing run per call and never reissues a position it has already given out, which is what makes the ordering the contract advertises hold.
A Sequence orders the writes made through one value. Ordering across separate Store instances stays implementation-defined, as Writer says.
func NewSequence ¶ added in v0.16.0
NewSequence spaces consecutive positions stride apart.
Pass one nanosecond unless the store's position type is coarser than the clock: a Cassandra TIMEUUID advances in 100-nanosecond ticks, so reserving at nanosecond spacing there would let distinct positions collapse onto one identifier.
func (*Sequence) Reserve ¶ added in v0.16.0
Reserve returns the first position of a contiguous run of count positions, each one stride after the last. Successive calls strictly increase even when the wall clock does not.
A count below one is a caller bug rather than a runtime condition — every store reserves the length of a batch it has already refused to build empty — so it panics instead of widening every call site with an unreachable branch.
type Store ¶
type Store interface {
ReadWriter
Clearer
}
Store is the ordinary per-conversation read/write/clear contract. Optional cross-conversation or retention capabilities remain separate interfaces.
type WindowStore ¶
type WindowStore struct {
// contains filtered or unexported fields
}
WindowStore projects reads to at most limit messages while preserving a merged system message followed by a suffix of complete conversation turns. A user message starts a turn; every following assistant and tool message remains in that turn until the next user message. Writes and clears pass through to the authoritative Store. Read returns ErrWindowTooSmall rather than splitting the newest complete turn, and the merged system message counts toward the configured limit.
func NewWindowStore ¶
func NewWindowStore(store Store, limit int) (WindowStore, error)
NewWindowStore projects reads to a bounded suffix of complete turns rather than a message count, because cutting mid-turn strands a tool result from the call that produced it and breaks their protocol relationship. Writes still reach the authoritative store, so windowing narrows what a model sees without discarding history.
func (WindowStore) Clear ¶
func (w WindowStore) Clear(ctx context.Context, conversationID ConversationID) error
func (WindowStore) Read ¶
func (w WindowStore) Read(ctx context.Context, conversationID ConversationID) ([]chat.Message, error)
func (WindowStore) Write ¶
func (w WindowStore) Write(ctx context.Context, conversationID ConversationID, messages ...chat.Message) (WriteOutcome, error)
type WriteOutcome ¶ added in v0.20.0
WriteOutcome describes the acknowledged facts of one Write attempt. It never implies idempotency or rollback. The zero value confirms that nothing was written. Accepted counts the confirmed prefix in argument order. Uncertain means additional messages after that prefix may have been stored; the caller must reconcile them before retrying. Otherwise all remaining messages are confirmed unwritten. All messages may be accepted even if ancillary work fails.
type Writer ¶
type Writer interface {
// Write validates and snapshots the full argument batch before appending it
// in argument order. The outcome must account for every acknowledged or
// uncertain effect even on error; validation failures return the zero outcome.
// A nil error acknowledges the complete batch.
Write(ctx context.Context, conversationID ConversationID, messages ...chat.Message) (WriteOutcome, error)
}
Writer appends messages to one conversation. Implementations preserve the order of messages within each call, validate and snapshot them before returning, and prevent later caller mutation from altering stored history. The relative order of concurrent calls and writes issued through distinct Store instances is implementation-defined.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package inmemory provides a zero-value-ready in-process history store.
|
Package inmemory provides a zero-value-ready in-process history store. |
|
Package storetest provides reusable conformance checks for history stores.
|
Package storetest provides reusable conformance checks for history stores. |