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 ¶
- Variables
- type AppendID
- type Batch
- func (b Batch) ConversationID() ConversationID
- func (b Batch) Digest() string
- func (b Batch) EncodedMessages() json.RawMessage
- func (b Batch) ID() AppendID
- func (b Batch) Len() int
- func (b Batch) MarshalJSON() ([]byte, error)
- func (b Batch) Messages() []chat.Message
- func (b *Batch) UnmarshalJSON(data []byte) error
- func (b Batch) Validate() error
- type Clearer
- type CommitError
- type ConversationID
- type Lister
- type Middleware
- type MiddlewareConfig
- type ReadWriter
- type Reader
- type Receipt
- type Store
- type WindowReader
- type Writer
Examples ¶
Constants ¶
This section is empty.
Variables ¶
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") )
var ( ErrNilReader = errors.New("history: nil reader") ErrNilWriter = errors.New("history: nil writer") )
var ( ErrInvalidWindow = errors.New("history: invalid message window") ErrWindowTooSmall = errors.New("history: message window too small") )
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
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 (Batch) ConversationID ¶ added in v0.60.0
func (b Batch) ConversationID() ConversationID
func (Batch) EncodedMessages ¶ added in v0.60.0
func (b Batch) EncodedMessages() json.RawMessage
func (Batch) MarshalJSON ¶ added in v0.60.0
func (*Batch) UnmarshalJSON ¶ added in v0.60.0
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 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
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.
Source Files
¶
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. |