history

package
v0.54.0 Latest Latest
Warning

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

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

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 reference implementation lives in core/history/inmemory.

MiddlewareConfig freezes one validated ConversationID at construction. The Host selects the partition by assembling the appropriate model binding.

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 + binary Core wire)
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

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidWindow  = errors.New("history: invalid message window")
	ErrWindowTooSmall = errors.New("history: message window too small")
)
View Source
var ErrInvalidConversationID = errors.New("history: invalid conversation ID")
View Source
var ErrInvalidWriteOutcome = errors.New("history: invalid write outcome")
View Source
var ErrNilStore = errors.New("history: nil store")
View Source
var ErrNilStream = errors.New("history: middleware: nil stream sequence")

Functions

This section is empty.

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
}

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 NewConversationID

func NewConversationID(value string) (ConversationID, error)

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)
}

type Middleware

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

Middleware replays and persists history around synchronous and streaming chat capabilities. It is immutable and as safe for concurrent use as its Store, but it does not serialize Read/model/Write turns of one conversation. Its zero value is invalid; construct it with NewMiddleware.

func NewMiddleware

func NewMiddleware(config MiddlewareConfig) (Middleware, error)
Example
package main

import (
	"context"
	"fmt"

	"github.com/Tangerg/scope/core/chat"
	"github.com/Tangerg/scope/core/history"
	"github.com/Tangerg/scope/core/history/inmemory"
)

func main() {
	store := new(inmemory.Store)
	middleware, err := history.NewMiddleware(history.MiddlewareConfig{Store: store, ConversationID: "customer-42"})
	if err != nil {
		panic(err)
	}
	model := middleware.Call(chat.ModelFunc(func(context.Context, *chat.Request) (*chat.Response, error) {
		return &chat.Response{Output: &chat.Output{Message: new(chat.NewAssistantMessage(chat.NewTextPart("Hello"))), FinishReason: chat.FinishReasonStop}}, nil
	}))
	request, err := chat.NewRequest(chat.NewUserMessage(chat.NewTextPart("Hi")))
	if err != nil {
		panic(err)
	}
	response, err := model.Call(context.Background(), request)
	if err != nil {
		panic(err)
	}
	messages, err := store.Read(context.Background(), "customer-42")
	if err != nil {
		panic(err)
	}
	fmt.Println(response.Text(), len(messages))
}
Output:
Hello 2

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. Bound calls reject invalid complete responses before applying the history retention policy.

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 MiddlewareConfig added in v0.54.0

type MiddlewareConfig struct {
	Store          ReadWriter
	ConversationID ConversationID
}

MiddlewareConfig binds history to one explicit partition. Calls that do not use history should use the undecorated model.

type ReadWriter

type ReadWriter interface {
	Reader
	Writer
}

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)
}

type Sequence added in v0.16.0

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

Sequence reserves contiguous, strictly increasing positions despite wall-clock regression or repeated instants. It never reissues a position through the same value; ordering across separate Store instances remains implementation-defined.

func NewSequence added in v0.16.0

func NewSequence(stride time.Duration) (*Sequence, error)

NewSequence spaces positions by stride. Use one nanosecond unless the store needs a coarser unit, such as TIMEUUID's 100-nanosecond ticks.

func (*Sequence) Reserve added in v0.16.0

func (s *Sequence) Reserve(count int) int64

Reserve returns the first position of count positions spaced by stride. Successive calls strictly increase even when the wall clock does not. Non-positive counts and reservations exceeding int64 panic without consuming positions.

type Store

type Store interface {
	ReadWriter
	Clearer
}

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)

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

type WriteOutcome struct {
	Accepted  int
	Uncertain bool
}

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.

func (WriteOutcome) ValidateFor added in v0.41.0

func (w WriteOutcome) ValidateFor(count int, err error) error

ValidateFor checks the outcome against the attempted batch size and error. Successful calls must acknowledge the full batch without uncertainty.

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 leaves the relative order of concurrent calls, or of writes through separate Store instances, implementation-defined.

Directories

Path Synopsis
Package inmemory provides the in-process reference history store.
Package inmemory provides the in-process reference history store.
Package storetest provides reusable conformance checks for history stores.
Package storetest provides reusable conformance checks for history stores.

Jump to

Keyboard shortcuts

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