Documentation
¶
Overview ¶
Package agentmemory is memory for Go agents over Open Responses: what the agent keeps between sessions about the user, the project and its own past work, in a scoped store with an append-only journal, changed only through tools and rendered into the instructions with a manifest for the session's provenance.
An Entry is one remembered thing: a kebab-case name unique within a Scope, Markdown content bounded by the store's limit, and metadata such as a description. A Store holds entries and a journal of every change; NewMemStore is the in-memory one, filestore the reference one a person can open, edit and commit, and sqlite the one with full-text search. Tools returns the four tools through which the model reaches a store, so every write is a function call in the transcript. Render turns the entries of the scopes a product names into the block it puts in agentturn's Config.Instructions, bounded in total, and the Manifest of what the block held.
The package depends on openresponses, agenttool and the standard library. The agent loop is never imported; a product wires the block and the tools into its configuration.
Index ¶
- Constants
- Variables
- func CheckEntry(e Entry, limit int, stored *Entry) error
- func Hash(content string) string
- func Match(e Entry, query string) bool
- func SessionFrom(ctx context.Context) string
- func Tools(store Store, scopes []Scope, opts ...ToolOption) []agenttool.Tool
- func Usage() string
- func ValidName(s string) bool
- func ValidScope(s Scope) bool
- func WithSession(ctx context.Context, id string) context.Context
- type Change
- type ConflictError
- type Entry
- type Manifest
- type ManifestEntry
- type MemOption
- type MemStore
- func (m *MemStore) Forget(ctx context.Context, scope Scope, name string) error
- func (m *MemStore) Get(_ context.Context, scope Scope, name string) (*Entry, error)
- func (m *MemStore) Journal(_ context.Context, after uint64) iter.Seq2[Change, error]
- func (m *MemStore) List(_ context.Context, scope Scope) ([]Entry, error)
- func (m *MemStore) MaxEntryBytes() int
- func (m *MemStore) Put(ctx context.Context, e Entry, opts ...PutOption) error
- func (m *MemStore) Search(_ context.Context, scopes []Scope, query string, limit int) ([]Entry, error)
- type PutOption
- type PutOptions
- type RenderOption
- type Scope
- type SizeError
- type Store
- type ToolOption
Constants ¶
const ( // MaxNameBytes bounds a scope or a name. MaxNameBytes = 64 // MaxMetaBytes bounds the meta of one entry, keys and values // together, separately from the content bound. MaxMetaBytes = 1 << 10 )
Limits on names and metadata that every store enforces.
const ( SaveTool = "memory_save" PatchTool = "memory_patch" ForgetTool = "memory_forget" SearchTool = "memory_search" )
The tools' names.
const ( // DefaultSearchLimit is how many entries a search returns when the // call names no limit. DefaultSearchLimit = 5 // DefaultSearchBytes bounds the content one search result shows; // matches past it are listed by name. DefaultSearchBytes = 16 << 10 )
Defaults for memory_search. A search result is appended to the transcript once and stays for the rest of the session, unlike the rendered block, so both are low against the render budget.
const DefaultMaxEntryBytes = 4 << 10
DefaultMaxEntryBytes is the per-entry bound every store starts with.
const DefaultMaxTotalBytes = 32 << 10
DefaultMaxTotalBytes is the bound Render starts with: the content of the included entries together.
const MetaDescription = "description"
MetaDescription is the meta key the index and the rendered block show beside an entry.
Variables ¶
var ( // ErrNotFound is returned for an entry that is not live. ErrNotFound = errors.New("agentmemory: no such entry") // ErrInvalid is returned for a scope, name, content or meta that // breaks the rules in [CheckEntry]. ErrInvalid = errors.New("agentmemory: invalid entry") // ErrTooLarge is returned for content over the store's bound; the // error is a [SizeError]. ErrTooLarge = errors.New("agentmemory: entry over the size limit") // ErrConflict is returned by a conditional Put whose precondition // fails; the error is a [ConflictError]. ErrConflict = errors.New("agentmemory: stored entry differs from the one expected") )
Errors a Store returns, each wrapped with the detail of the case.
Functions ¶
func CheckEntry ¶
CheckEntry reports what a store refuses: an invalid scope or name, empty content or content that is not UTF-8, a meta key that is not a name or is reserved ("name", "updated"), a meta value with a line break or surrounding space, meta over MaxMetaBytes together, each as ErrInvalid; and content over limit as a SizeError, with stored's size when the entry exists. Every store calls it in Put, so the rules are the same everywhere.
func Hash ¶
Hash returns "sha256:" and the hex digest of content, the form Entry.Hash and Change.Prev carry.
func Match ¶
Match reports whether e matches query as the reference stores search: every whitespace-separated word of query appears, ignoring case, in the entry's name, one of its meta values or its content. An empty query matches every entry.
func SessionFrom ¶
SessionFrom returns the session ID attached to ctx, or "".
func Tools ¶
func Tools(store Store, scopes []Scope, opts ...ToolOption) []agenttool.Tool
Tools returns the four tools through which the model reaches store, restricted to the scopes the product allows: memory_save, memory_patch, memory_forget and memory_search. A call that names a scope outside the list is an error the model sees; a call that omits the scope uses the first. Every write is a function call in the transcript, and the store's journal records it under the session on the context, see WithSession. Tools panics with no scopes, since a tool set that can reach nothing is a programming error.
func Usage ¶
func Usage() string
Usage is one paragraph telling the model how to use the memory tools over the block Render produced. A product appends it to its instructions when it offers Tools.
func ValidName ¶
ValidName reports whether s is kebab-case: lowercase ASCII letters and digits in groups joined by single hyphens, between 1 and MaxNameBytes bytes. Scopes, names and meta keys all follow it, so a name is a file name, a directory name and a frontmatter key without escaping.
func ValidScope ¶
ValidScope reports whether s is a well-formed scope: see ValidName.
func WithSession ¶
WithSession attaches the session ID that writes through ctx, so a store records it on each change. One store serves many sessions at once, so the attribution travels on the context rather than the store; a product sets it on the context it runs the agent with.
Types ¶
type Change ¶
type Change struct {
// Seq is the record's position in the store's journal, monotonic
// within one store and the cursor [Store.Journal] takes. It
// promises nothing across stores.
Seq uint64 `json:"seq"`
// Entry is the entry after the change; a tombstone carries the
// last content with Deleted set.
Entry Entry `json:"entry"`
// Prev is the hash of the content the change replaced, "" for a
// create. A reader chains records through it and sees a fork where
// two writers built on one predecessor.
Prev string `json:"prev,omitempty"`
// Session is the session that wrote the change, from
// [WithSession], or "" for a person or an unattributed caller.
Session string `json:"session,omitempty"`
// At is when the change was made, for display and the record. It
// never orders records; Seq does.
At time.Time `json:"at"`
}
Change is one journal record: the entry as it was after the change, what it replaced, and who made it.
type ConflictError ¶
ConflictError is the ErrConflict a conditional Put returns: the hash the caller expected and the one the store holds, "" for no entry on either side.
func (*ConflictError) Error ¶
func (e *ConflictError) Error() string
type Entry ¶
type Entry struct {
Scope Scope `json:"scope"`
// Name is kebab-case and unique within the scope.
Name string `json:"name"`
// Content is Markdown, bounded by the store's limit.
Content string `json:"content"`
// Meta is what the product and the model add beside the content:
// the description the index and the block show, a type, anything
// else. Keys are kebab-case; values are one line each.
Meta map[string]string `json:"meta,omitempty"`
// Hash is "sha256:" and the hex digest of Content. A store sets it.
Hash string `json:"hash"`
// Updated is when the store last wrote the entry. A store sets it.
Updated time.Time `json:"updated"`
// Deleted marks a tombstone in the journal; Content is then the
// last content the entry held. A store never returns one from Get
// or List.
Deleted bool `json:"deleted,omitempty"`
}
Entry is one remembered thing.
func (Entry) Description ¶
Description returns the entry's description, or "".
type Manifest ¶
type Manifest struct {
// Entries are the included entries in block order.
Entries []ManifestEntry `json:"entries"`
// Omitted are the entries the total bound left out, in the order
// they would have appeared.
Omitted []ManifestEntry `json:"omitted,omitempty"`
}
Manifest lists what Render put in the block, for the session's provenance: a product records it beside the turn, in a custom entry under "agentmemory:render", so a later reader knows which memories the model had and which it did not.
func Render ¶
func Render(ctx context.Context, s Store, scopes []Scope, opts ...RenderOption) (string, Manifest, error)
Render returns the in-context block: a header with the counts and the budget, then one section per scope in the order given, one heading per entry in list order carrying its size and the store's limit and its description, then the content verbatim. Entries are included until the next would take the content total over the bound; it and everything after it are listed under their scopes as omitted, so the model knows what memory_search can fetch. The output is determined by the store's state and the bounds alone, so an unchanged store renders the same bytes and costs nothing in the session, and the manifest's hashes are the hashes of the content the block shows.
Content is not transformed. A heading inside an entry's content at level one to three would read as structure, so the tool descriptions ask the model for level four or none.
type ManifestEntry ¶
type ManifestEntry struct {
Scope Scope `json:"scope"`
Name string `json:"name"`
Hash string `json:"hash"`
Bytes int `json:"bytes"`
}
ManifestEntry names one entry by scope and name, with the hash and size of the content the block held or left out.
type MemOption ¶
type MemOption func(*MemStore)
MemOption configures NewMemStore.
func WithClock ¶
WithClock sets the clock that stamps Updated and At, for tests that want a fixed one.
func WithMaxEntryBytes ¶
WithMaxEntryBytes sets the content bound; the default is DefaultMaxEntryBytes.
type MemStore ¶
type MemStore struct {
// contains filtered or unexported fields
}
MemStore is the in-memory Store: the reference for the contract, the store for tests, and a store for a product whose memory need not outlive the process. It is safe for concurrent use.
func NewMemStore ¶
NewMemStore returns an empty in-memory store.
func (*MemStore) MaxEntryBytes ¶
MaxEntryBytes implements Store.
type PutOption ¶
type PutOption func(*PutOptions)
PutOption configures one Put.
func IfHash ¶
IfHash makes the Put conditional: it fails with ErrConflict when the stored content hash is not h, so a write built on a stale read fails instead of clobbering. "" means the entry must not exist.
type PutOptions ¶
type PutOptions struct {
// IfHash is the hash the stored entry must have when Conditional
// is set, "" meaning the entry must not exist.
IfHash string
Conditional bool
}
PutOptions is the resolved form of a Put's options, for a Store to read through ResolvePutOptions.
func ResolvePutOptions ¶
func ResolvePutOptions(opts ...PutOption) PutOptions
ResolvePutOptions applies the options to a zero PutOptions.
func (PutOptions) Check ¶
func (o PutOptions) Check(scope Scope, name string, stored *Entry) error
Check reports an ErrConflict when the options are conditional and stored, the live entry or nil for none, does not satisfy them. A store calls it under its write lock, after reading the entry.
type RenderOption ¶
type RenderOption func(*renderOptions)
RenderOption configures Render.
func WithMaxTotalBytes ¶
func WithMaxTotalBytes(n int) RenderOption
WithMaxTotalBytes sets the total content bound; the default is DefaultMaxTotalBytes, and a value under one means the default.
type Scope ¶
type Scope string
Scope says whose memory an entry is. Products define the values; "user", "project" and "session" are the conventional three. A scope has the same grammar as a name: see ValidName.
type SizeError ¶
type SizeError struct {
Scope Scope
Name string
// Size is the attempted content size and Limit the bound.
Size, Limit int
// Stored is the size the live entry holds, or -1 when there is
// none.
Stored int
}
SizeError is the ErrTooLarge a Put returns, with the numbers the model needs to trim: the attempted size, the limit, and what the entry holds now.
type Store ¶
type Store interface {
// Get returns one live entry, or [ErrNotFound].
Get(ctx context.Context, scope Scope, name string) (*Entry, error)
// List returns the live entries of a scope by name. An unknown
// scope lists nothing.
List(ctx context.Context, scope Scope) ([]Entry, error)
// Put creates or replaces the whole entry, content and meta, and
// appends the new state to the journal. It refuses an invalid
// entry with [ErrInvalid], content over [Store.MaxEntryBytes] with
// [ErrTooLarge], and, under [IfHash], a stored hash other than the
// one expected with [ErrConflict]. Hash and Updated on e are
// ignored and set by the store.
Put(ctx context.Context, e Entry, opts ...PutOption) error
// Forget writes a tombstone: the entry leaves Get and List, and
// the journal keeps its last content. A missing entry is
// [ErrNotFound].
Forget(ctx context.Context, scope Scope, name string) error
// Search returns the live entries of the scopes that match query,
// at most limit of them when limit is positive. What matches and
// in what order is the store's; the reference stores use [Match]
// and list by scope then name, and sqlite ranks by full-text
// relevance.
Search(ctx context.Context, scopes []Scope, query string, limit int) ([]Entry, error)
// Journal yields every change with Seq greater than after, in
// order; 0 reads from the beginning. A reader that stops early may
// resume with the last Seq it saw.
Journal(ctx context.Context, after uint64) iter.Seq2[Change, error]
// MaxEntryBytes is the content size Put refuses to exceed.
MaxEntryBytes() int
}
Store holds entries and the journal of every change to them. Every implementation passes storetest.
type ToolOption ¶
type ToolOption func(*toolOptions)
ToolOption configures Tools.
func WithSearchBytes ¶
func WithSearchBytes(n int) ToolOption
WithSearchBytes sets the most content one memory_search result shows; the default is DefaultSearchBytes, and a value under one means the default.
func WithSearchLimit ¶
func WithSearchLimit(n int) ToolOption
WithSearchLimit sets the number of entries memory_search returns when the call names no limit; the default is DefaultSearchLimit, and a value under one means the default.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package filestore is the reference agentmemory.Store: one directory per scope, one Markdown file per entry with a frontmatter, an INDEX.md per scope listing every live entry, and one append-only journal.jsonl at the root.
|
Package filestore is the reference agentmemory.Store: one directory per scope, one Markdown file per entry with a frontmatter, an INDEX.md per scope listing every live entry, and one append-only journal.jsonl at the root. |
|
Package storetest is the conformance suite for agentmemory.Store implementations.
|
Package storetest is the conformance suite for agentmemory.Store implementations. |