memory

package
v0.0.35 Latest Latest
Warning

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

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

Documentation

Overview

Package memory implements harness pattern 3 (tiered memory): a conservative, cross-session memory facility exposed to the model as two tools (Remember and Recall) backed by a pluggable tool.MemoryStore.

This package contains BOTH the file-backed store adapter AND the tool.Tool values that drive it. They are kept together deliberately: the tools are thin adapters over the store seam (constructor-injected, mirroring NewShellTool), and shipping them as one opt-in unit lets the composition root wire memory with a single import. The tools depend only on the tool.MemoryStore interface, so a fake in-memory store can stand in for tests.

SCOPING: a Store is per-PROJECT. The composition root calls New(dir) once per project/workspace directory; entries persist across process restarts for that directory and are isolated from other projects. See tool.MemoryStore.

Index

Constants

View Source
const (
	RememberToolName      = "Remember"
	RecallToolName        = "Recall"
	SearchMemoryToolName  = "SearchMemory"
	InspectMemoryToolName = "InspectMemory"
	ForgetMemoryToolName  = "ForgetMemory"
	UndoMemoryToolName    = "UndoMemory"
)

Tool names are kept here for compatibility with root composition callers.

View Source
const (
	RememberUserToolName      = "RememberUser"
	RecallUserToolName        = "RecallUser"
	SearchUserModelToolName   = "SearchUserModel"
	InspectUserMemoryToolName = "InspectUserMemory"
	ForgetUserMemoryToolName  = "ForgetUserMemory"
	UndoUserMemoryToolName    = "UndoUserMemory"
)

Catalog names of the user-scoped portable memory tools.

Variables

This section is empty.

Functions

func NewCallerStore

func NewCallerStore(store tool.MemoryStore, project bool) tool.MemoryStore

NewCallerStore returns a store partitioned by verified caller and, when project is true, by workspace. The returned store advertises MemoryLifecycleStore exactly when the backing store does.

func NewNamespacedStore

func NewNamespacedStore(store tool.MemoryStore, namespace string) tool.MemoryStore

NewNamespacedStore confines a MemoryStore to namespace. Namespace is an adapter-private boundary, not a model-visible key prefix. The returned store advertises MemoryLifecycleStore exactly when the backing store does.

func NewRecallTool

func NewRecallTool(store tool.MemoryStore) tool.Tool

NewRecallTool returns the portable project-scoped Recall implementation.

func NewRememberTool

func NewRememberTool(store tool.MemoryStore) tool.Tool

NewRememberTool returns the portable project-scoped Remember implementation.

func NewSearchMemoryTool

func NewSearchMemoryTool(store tool.MemoryStore) tool.Tool

NewSearchMemoryTool returns the portable project-scoped Search implementation.

func NewUserModelTools

func NewUserModelTools(store tool.MemoryStore) []tool.Tool

NewUserModelTools returns the user-scoped portable family. Lifecycle tools are included only when store implements tool.MemoryLifecycleStore.

func Register

func Register(cat *tool.Catalog, store tool.MemoryStore) error

Register adds the applicable project family to cat.

func RegisterUserModel

func RegisterUserModel(cat *tool.Catalog, store tool.MemoryStore) error

RegisterUserModel adds the applicable user family to cat.

func Tools

func Tools(store tool.MemoryStore) []tool.Tool

Tools returns the project family. Lifecycle tools are included only when the store advertises tool.MemoryLifecycleStore.

func WithWorkspace

func WithWorkspace(ctx context.Context, workspace string) context.Context

WithWorkspace annotates a run context for the caller-scoped project memory adapter. It is intentionally adapter-local: workspaces are already session state and do not widen any engine port.

func WorkspaceFromContext added in v0.0.24

func WorkspaceFromContext(ctx context.Context) string

WorkspaceFromContext returns the private runtime workspace root attached by server composition. It is intentionally not derived from a durable EnvironmentRef, whose ID is provider-opaque.

Types

type CallerStore

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

CallerStore chooses a namespace from the verified caller on each operation. Project stores additionally bind that namespace to the run's workspace.

func (*CallerStore) Forget

func (s *CallerStore) Forget(ctx context.Context, key string) error

Forget removes key from the verified caller's namespace.

func (*CallerStore) Index

func (s *CallerStore) Index(ctx context.Context) ([]tool.MemoryEntry, error)

Index returns verified caller entries without their values.

func (*CallerStore) List

func (s *CallerStore) List(ctx context.Context, prefix string) ([]tool.MemoryEntry, error)

List returns verified caller entries matching prefix.

func (*CallerStore) Recall

func (s *CallerStore) Recall(ctx context.Context, key string) (tool.MemoryEntry, bool, error)

Recall retrieves key from the verified caller's namespace.

func (*CallerStore) RememberEntry

func (s *CallerStore) RememberEntry(ctx context.Context, entry tool.MemoryEntry) error

RememberEntry stores entry in the verified caller's namespace.

func (*CallerStore) Search

func (s *CallerStore) Search(ctx context.Context, query string, limit int) ([]tool.MemoryEntry, error)

Search ranks entries in the verified caller's namespace.

type NamespacedStore

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

NamespacedStore presents one logical MemoryStore namespace over a shared backing store. Every operation, including index/search and consolidation, is confined to namespace; callers never see the internal key prefix.

func (*NamespacedStore) Forget

func (s *NamespacedStore) Forget(ctx context.Context, key string) error

Forget removes key from this namespace.

func (*NamespacedStore) Index

func (s *NamespacedStore) Index(ctx context.Context) ([]tool.MemoryEntry, error)

Index returns namespace entries without their values.

func (*NamespacedStore) List

func (s *NamespacedStore) List(ctx context.Context, prefix string) ([]tool.MemoryEntry, error)

List returns namespace entries matching prefix.

func (*NamespacedStore) Recall

func (s *NamespacedStore) Recall(ctx context.Context, key string) (tool.MemoryEntry, bool, error)

Recall retrieves key from this namespace.

func (*NamespacedStore) RememberEntry

func (s *NamespacedStore) RememberEntry(ctx context.Context, entry tool.MemoryEntry) error

RememberEntry stores entry under this namespace.

func (*NamespacedStore) Search

func (s *NamespacedStore) Search(ctx context.Context, query string, limit int) ([]tool.MemoryEntry, error)

Search ranks entries within this namespace.

type Store

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

Store is a file-backed, cross-process-safe tool.MemoryStore. It persists entries as a single JSON document at <dir>/memory.json and commits every write atomically (temp file + rename) so a crash mid-write cannot corrupt or truncate the on-disk file. A fresh Store opened over the same dir sees previously written entries, giving durability across process restarts.

Concurrency / locking. The store is safe for both in-process and cross-process concurrent use, and — critically — does not LOSE updates under either:

  • In-process: s.mu serialises every method on a single Store, and is also held across the full read-modify-write of a mutation. This is the only lock that protects the flock handle, which is NOT goroutine-safe when shared across goroutines on one fd.
  • Cross-process (several mecated/mecatui instances, agent-team / subagent runs sharing one memory.json): a gofrs/flock advisory lock on the STABLE sentinel <dir>/memory.lock guards the read-modify-write. Writes (RememberEntry, Remember, Forget) take an EXCLUSIVE lock; reads (Recall, List, Index) take a SHARED lock. The lock spans the whole load→mutate→save sequence, so two processes can no longer interleave read-modify-write and clobber each other (the lost-update bug the bare temp+rename did not prevent).

Acquisition order is always s.mu THEN flock; the flock is acquired with a bounded TryLockContext/TryRLockContext (lockRetryDelay / lockTimeout, also honouring ctx cancellation) so a stuck holder fails loud instead of deadlocking, and released (Unlock) before return on every path including errors.

INVARIANT — at most ONE *Store per directory per process. gofrs/flock uses BSD flock(2), which contends across file descriptors even WITHIN a single process. Two *Store values opened over the SAME dir in the SAME process therefore hold DISTINCT fds on the sentinel and would self-deadlock: the second writer's exclusive acquire blocks on the first's lock until the lockTimeout expires (a loud error, but a needless one). The composition root upholds this today by constructing one shared *Store per project (see internal/app). If per-session memory is ever needed, SHARE a single *Store keyed by absolute dir (the way the session-engine map is keyed) rather than calling New per session — do NOT open a second *Store over a dir already owned in-process.

func New

func New(dir string) (*Store, error)

New constructs a file-backed Store rooted at dir, creating dir (and parents) if it does not exist. The store is scoped to dir: it owns <dir>/memory.json and the cross-process lock sentinel <dir>/memory.lock. Pass a per-project directory so memory is isolated per project.

Construct AT MOST ONE *Store per dir per process. Because the cross-process lock uses BSD flock(2) (per-fd, cross-fd-contending even in one process), a SECOND *Store opened over the same dir in this process would self-deadlock its own lock acquisition until the lockTimeout, since the two *Store values hold separate fds on the sentinel. Share one *Store keyed by absolute dir instead. See the Store type doc for the full rationale.

func (*Store) Forget

func (s *Store) Forget(ctx context.Context, key string) error

Forget deletes the active entry for key. It remains idempotent for legacy callers, while an existing value is retained as lifecycle history plus a tombstone.

func (*Store) ForgetVersioned

func (s *Store) ForgetVersioned(ctx context.Context, key string, expected tool.MemoryVersion) (tool.MemoryRecord, error)

ForgetVersioned atomically appends a tombstone when expected is current.

func (*Store) Index

func (s *Store) Index(ctx context.Context) ([]tool.MemoryEntry, error)

Index returns the tier-0 routing table: every entry with the VALUE OMITTED and Description filled (explicit, else derived from the value's first line), sorted by key. It applies NO size cap — the consumer (the prompt assembler) caps and renders. It is the cheap, always-in-context summary view.

func (*Store) Inspect

func (s *Store) Inspect(ctx context.Context, key string) (tool.MemoryRecord, bool, error)

Inspect returns the current state and complete history, including deleted tombstones. Legacy flat records are projected as a stable imported baseline without rewriting memory.json.

func (*Store) List

func (s *Store) List(ctx context.Context, prefix string) ([]tool.MemoryEntry, error)

List returns all entries whose key has the given prefix, sorted by key. An empty prefix returns every entry.

func (*Store) Recall

func (s *Store) Recall(ctx context.Context, key string) (tool.MemoryEntry, bool, error)

Recall returns the entry for the exact key. A miss is (zero, false, nil).

func (*Store) Remember

func (s *Store) Remember(ctx context.Context, key, value string) error

Remember stores value under key with no explicit description (the index derives one from the value), overwriting any existing entry and bumping its UpdatedAt. An empty key is rejected. It is a convenience wrapper over RememberEntry, kept so callers that do not care about descriptions stay unchanged. It is intentionally NOT part of tool.MemoryStore — the interface carries RememberEntry only; this concrete convenience survives for direct *Store users.

func (*Store) RememberEntry

func (s *Store) RememberEntry(ctx context.Context, e tool.MemoryEntry) error

RememberEntry stores e, overwriting any existing entry under e.Key and bumping UpdatedAt to now. e.Description is stored as-is (empty is allowed; the index derives one). An empty key is rejected.

func (*Store) RememberIfCurrent

func (s *Store) RememberIfCurrent(ctx context.Context, entry tool.MemoryEntry, expected tool.MemoryCurrent) (tool.MemoryRecord, error)

RememberIfCurrent performs a presence-and-version CAS in the same flocked transaction.

func (*Store) RememberVersioned

func (s *Store) RememberVersioned(ctx context.Context, entry tool.MemoryEntry, expected tool.MemoryVersion) (tool.MemoryRecord, error)

RememberVersioned atomically creates or replaces a record when expected is the current opaque version. A legacy flat record is materialized as the first imported revision inside the same locked write.

func (*Store) RetireDuplicate

func (s *Store) RetireDuplicate(ctx context.Context, survivorKey string, survivorVersion tool.MemoryVersion, sourceKey string, sourceVersion tool.MemoryVersion) (tool.MemoryRecord, error)

RetireDuplicate atomically verifies the active survivor and source revisions and appends a source tombstone in one exclusive load/mutate/save transaction.

func (*Store) Search

func (s *Store) Search(ctx context.Context, query string, k int) ([]tool.MemoryEntry, error)

Search ranks entries by BM25 lexical relevance to query (over each entry's key + derived description + value) and returns the top k best-first, with the VALUE OMITTED and Description filled — mirroring Index's result shape. Ranking runs INSIDE the shared lock, over the freshly loaded data, so it sees a consistent snapshot and never races a concurrent write. An empty or whitespace-only query short-circuits to an empty result (no lock, no error); k <= 0 uses the default page size. See bm25Rank for the scoring/ordering rules.

func (*Store) SynthesizeReplacement

func (s *Store) SynthesizeReplacement(ctx context.Context, survivor tool.MemoryEntry, survivorVersion tool.MemoryVersion, sourceKeys []string, sourceVersions []tool.MemoryVersion) (tool.MemoryRecord, error)

SynthesizeReplacement atomically verifies every participant, appends a new survivor revision with the reviewed bytes, and tombstones all sources in one exclusive load/mutate/save transaction. Undo remains per-key and is intentionally not exposed as a grouped inverse of this operation.

func (*Store) UndoLatest

func (s *Store) UndoLatest(ctx context.Context, key string, expected tool.MemoryVersion) (tool.MemoryRecord, error)

UndoLatest appends a compensating revision for the newest mutation not already compensated. Recording the target version makes repeated undo walk backward and prevents concurrent callers from reversing one mutation twice.

Jump to

Keyboard shortcuts

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