history

package
v0.60.0 Latest Latest
Warning

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

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

Documentation

Overview

Package history defines provider-neutral conversation history contracts and model middleware. Persistent backends live in independent historystores modules; the reference implementation lives in core/history/inmemory.

Each Batch owns an immutable append identity and canonical message snapshot. Writer atomically accepts the complete batch, including its deduplication evidence, and returns a Receipt. A retry submits the same Batch: identical content returns the same receipt, while different content conflicts. Missing acknowledgment never authorizes a new append identity. Message retention in an open conversation must not discard receipt evidence. Clear permanently closes a ConversationID, so delayed appends cannot resurrect deleted history. A new conversation needs a new identity.

Middleware binds one ConversationID to explicit Reader and Writer capabilities. Requests supply a current system prefix and one user-led open turn. Tool follow-ups resubmit that turn's user message, assistant tool calls, and tool results. The stored history supplies prior completed turns; a completed invocation appends its final assistant and open turn in one Batch. Callers supplying a complete transcript use the undecorated model.

CommitError preserves the Batch and any confirmed Receipt after generation succeeds but persistence fails. A host can retry that Batch without repeating model generation. The completed response and usage remain available to the caller. Invalid input fails before store or provider I/O.

WindowReader is a read projection that retains complete user-led turns, including their tool exchanges. Compose it with an explicit Writer; a read projection does not acquire write authority. The host owns conversation turn ordering: concurrent-safe store methods do not isolate the entire Read/model/Append sequence across calls or host instances.

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 (
	ErrInvalidAppendID    = errors.New("history: invalid append ID")
	ErrInvalidBatch       = errors.New("history: invalid append batch")
	ErrInvalidReceipt     = errors.New("history: invalid append receipt")
	ErrAppendConflict     = errors.New("history: append ID already names different messages")
	ErrConversationClosed = errors.New("history: conversation is permanently closed")
)
View Source
var (
	ErrNilReader = errors.New("history: nil reader")
	ErrNilWriter = errors.New("history: nil writer")
)
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")

Functions

This section is empty.

Types

type AppendID added in v0.60.0

type AppendID string

AppendID names one immutable append within a conversation. A retry must use the same ID and the same complete batch, including after acknowledgment loss.

func NewAppendID added in v0.60.0

func NewAppendID(value string) (AppendID, error)

func (AppendID) String added in v0.60.0

func (a AppendID) String() string

func (AppendID) Validate added in v0.60.0

func (a AppendID) Validate() error

type Batch added in v0.60.0

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

Batch freezes the complete append before its first write. Canonical encoding and its digest come from this owner, so adapters cannot disagree about whether a repeated identity carries the same content. Object key order and insignificant JSON whitespace do not affect identity; string values and number spellings do. Tool-call arguments are strings and retain the model's exact text.

func NewBatch added in v0.60.0

func NewBatch(conversationID ConversationID, id AppendID, messages ...chat.Message) (Batch, error)

func (Batch) ConversationID added in v0.60.0

func (b Batch) ConversationID() ConversationID

func (Batch) Digest added in v0.60.0

func (b Batch) Digest() string

func (Batch) EncodedMessages added in v0.60.0

func (b Batch) EncodedMessages() json.RawMessage

func (Batch) ID added in v0.60.0

func (b Batch) ID() AppendID

func (Batch) Len added in v0.60.0

func (b Batch) Len() int

func (Batch) MarshalJSON added in v0.60.0

func (b Batch) MarshalJSON() ([]byte, error)

func (Batch) Messages added in v0.60.0

func (b Batch) Messages() []chat.Message

func (*Batch) UnmarshalJSON added in v0.60.0

func (b *Batch) UnmarshalJSON(data []byte) error

func (Batch) Validate added in v0.60.0

func (b Batch) Validate() error

type Clearer

type Clearer interface {
	// Clear atomically closes this identity, including one not yet written. Closed
	// conversations read as empty and disappear from listing. Later appends,
	// including retries of accepted batches, return ErrConversationClosed.
	// Physical message cleanup can follow closure; Clear returns nil only when
	// cleanup completes. A cleanup error leaves the identity closed, and another
	// Clear retries cleanup. Closure evidence never expires; a new conversation
	// requires a new ConversationID. Permanent closure evidence can replace the
	// closed conversation's individual append receipts.
	Clear(ctx context.Context, conversationID ConversationID) error
}

type CommitError added in v0.20.0

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

CommitError retains an already completed generation's immutable append, so callers can retry persistence without invoking the model again. Receipt keeps acknowledged commit evidence even if an ancillary operation also failed.

func (*CommitError) Batch added in v0.60.0

func (c *CommitError) Batch() Batch

func (*CommitError) Error added in v0.20.0

func (c *CommitError) Error() string

func (*CommitError) Receipt added in v0.60.0

func (c *CommitError) Receipt() Receipt

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; closed conversations are omitted.
	// Concurrent appends may appear 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 Reader and Writer, but it does not serialize Read/model/Append 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{Reader: store, Writer: 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 {
	Reader         Reader
	Writer         Writer
	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 and closed 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 Receipt added in v0.60.0

type Receipt struct {
	ConversationID ConversationID
	ID             AppendID
	Digest         string
}

Receipt acknowledges the entire named batch. A zero receipt proves nothing about a failed attempt; submitting the same Batch safely resolves that doubt. Stores return a receipt only after their atomic append is acknowledged.

func (Receipt) ValidateFor added in v0.60.0

func (r Receipt) ValidateFor(batch Batch) error

type Store

type Store interface {
	ReadWriter
	Clearer
}

type WindowReader added in v0.60.0

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

WindowReader 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. This projection owns no write or clear authority. Read returns ErrWindowTooSmall rather than splitting the newest complete turn, and the merged system message counts toward the configured limit.

func NewWindowReader added in v0.60.0

func NewWindowReader(reader Reader, limit int) (WindowReader, error)

func (WindowReader) Read added in v0.60.0

func (w WindowReader) Read(ctx context.Context, conversationID ConversationID) ([]chat.Message, error)

type Writer

type Writer interface {
	// Append accepts one immutable batch under its stable identity. An identical
	// retry returns the original receipt without appending again; changed content
	// returns ErrAppendConflict. Errors without a valid receipt leave the outcome
	// unconfirmed and never authorize submitting under a new identity.
	// The closed-conversation check, deduplication record, and message append share
	// one atomic boundary. While the conversation is open, message expiration must
	// not erase deduplication evidence.
	Append(ctx context.Context, batch Batch) (Receipt, error)
}

Writer orders each complete batch atomically. Relative ordering of concurrent batches is implementation-defined; messages within a batch retain their order.

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