memory

package
v0.0.0-...-41a1881 Latest Latest
Warning

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

Go to latest
Published: Nov 11, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package memory 提供长期记忆的存储、召回和冲突处理领域模型。

该包不依赖 session 或 turn。Turn 只负责在正确的生命周期边界调用 Service,并把返回的 Snapshot 渲染成 request-only 上下文。

Index

Constants

View Source
const (
	DefaultMemoryContextTokenBudget = 2000
	DefaultMemoryCoreTokenBudget    = 800
	DefaultMemoryRecallTokenBudget  = 1200

	DefaultMemorySearchLimit       = 6
	DefaultMemorySearchTokenBudget = 1200
	DefaultMemorySearchEntryBudget = 160
	DefaultMemoryGetTokenBudget    = 2000
	DefaultMemoryGetContentBudget  = 1500
	DefaultMemoryQueryTokenBudget  = 160
	DefaultMemoryMaxSearchTerms    = 24
)

The budgets below are model-visible budgets. Storage is intentionally not bounded by them; only a rendered context or a tool response is bounded.

Variables

View Source
var ErrMaintenanceConflict = errors.New("maintenance operation conflict")
View Source
var ErrNotFound = errors.New("memory entry not found")
View Source
var ErrScopeForbidden = errors.New("memory scope is not permitted")

ErrScopeForbidden means a model-level memory operation attempted to leave the scope configured for the current Agent. Settings and migration use the explicit control-plane methods and may inspect both scopes.

Functions

This section is empty.

Types

type BoundedText

type BoundedText struct {
	Text           string `json:"text"`
	OriginalTokens int    `json:"original_tokens"`
	ReturnedTokens int    `json:"returned_tokens"`
	Truncated      bool   `json:"truncated"`
}

BoundedText is the observable result of clipping text for a model-facing response. OriginalTokens lets the model distinguish a complete value from a preview without receiving the original payload.

func BoundText

func BoundText(text string, maxTokens int) BoundedText

type Candidate

type Candidate struct {
	Request RememberRequest `json:"request"`
}

Candidate is model-inferred information awaiting serial consolidation. A candidate is never directly visible to Recall; the consolidator first stages it as pending and only promotes conflict-free candidates.

type CandidateExtractor

type CandidateExtractor interface {
	Extract(context.Context, ExtractionInput) ([]Candidate, error)
}

CandidateExtractor is an optional extension point. Implementations must be cancellation-aware and must not mutate the input slice.

type CandidatePipeline

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

CandidatePipeline owns a bounded background extraction queue. One pipeline is created per Agent runtime, so its single worker also provides the per-Agent serialization boundary required by the memory contract.

A nil extractor or consolidator makes the pipeline inert. This is the deliberate default: merely enabling Memory v2 does not spend LLM tokens on automatic extraction.

func NewCandidatePipeline

func NewCandidatePipeline(
	extractor CandidateExtractor,
	consolidator Consolidator,
	queueSize int,
	onError func(error),
) *CandidatePipeline

NewCandidatePipeline creates an optional asynchronous pipeline. queueSize is bounded so compression cannot create unbounded memory work when a model or SQLite store is slow.

func (*CandidatePipeline) Close

func (p *CandidatePipeline) Close() error

Close cancels extraction and prevents late compression callbacks from sending on a closed channel. Extraction implementations must honor ctx.

func (*CandidatePipeline) SetCoreBudget

func (p *CandidatePipeline) SetCoreBudget(tokens int)

func (*CandidatePipeline) SetOnChange

func (p *CandidatePipeline) SetOnChange(fn func(ConsolidationReport))

SetOnChange attaches a metadata-only callback. It is intentionally separate from construction so the runtime can bind its publisher after allocation.

func (*CandidatePipeline) Submit

func (p *CandidatePipeline) Submit(input ExtractionInput) bool

Submit queues a defensive copy and returns false when automatic extraction is disabled or the bounded queue is full. The caller must treat false as a dropped optional background task; the active Turn is never affected.

type CandidateSubmitter

type CandidateSubmitter interface {
	Submit(ExtractionInput) bool
}

CandidateSubmitter is the small boundary used by compression. Keeping this interface separate means compression does not know about queues, stores or Turn lifecycle.

type Conflict

type Conflict struct {
	ID                string    `json:"id"`
	Candidate         Entry     `json:"candidate"`
	Existing          []Entry   `json:"existing"`
	ExistingRevisions []int64   `json:"existing_revisions"`
	Relation          string    `json:"relation"`
	Description       string    `json:"description"`
	StoreRevision     int64     `json:"store_revision"`
	CreatedAt         time.Time `json:"created_at"`
}

type ConflictDecision

type ConflictDecision string
const (
	ConflictKeepOld  ConflictDecision = "keep_old"
	ConflictUseNew   ConflictDecision = "use_new"
	ConflictKeepBoth ConflictDecision = "keep_both"
	ConflictCancel   ConflictDecision = "cancel"
)

type ConsolidationReport

type ConsolidationReport struct {
	CandidateCount   int   `json:"candidate_count"`
	Changed          bool  `json:"changed"`
	Added            int   `json:"added"`
	Duplicates       int   `json:"duplicates"`
	PendingConflicts int   `json:"pending_conflicts"`
	Superseded       int   `json:"superseded"`
	StoreRevision    int64 `json:"store_revision"`
}

type Consolidator

type Consolidator interface {
	Consolidate(context.Context, []Candidate) ([]WriteResult, error)
}

Consolidator serializes candidate writes for one Agent. It must never approve a conflict automatically; conflicting candidates remain pending.

type Entry

type Entry struct {
	ID             string            `json:"id"`
	Scope          Scope             `json:"scope"`
	Tier           Tier              `json:"tier"`
	Kind           Kind              `json:"kind"`
	SemanticKey    string            `json:"semantic_key,omitempty"`
	Subject        string            `json:"subject,omitempty"`
	Predicate      string            `json:"predicate,omitempty"`
	Value          any               `json:"value,omitempty"`
	Qualifiers     map[string]string `json:"qualifiers,omitempty"`
	Cardinality    string            `json:"cardinality,omitempty"`
	Content        string            `json:"content"`
	NormalizedText string            `json:"normalized_content,omitempty"`
	ContentHash    string            `json:"content_hash,omitempty"`
	Status         Status            `json:"status"`
	Importance     int               `json:"importance"`
	Confidence     int               `json:"confidence"`
	Sensitivity    string            `json:"sensitivity,omitempty"`
	SourceType     string            `json:"source_type"`
	SourceSession  string            `json:"source_session_id,omitempty"`
	SourceMessage  string            `json:"source_message_id,omitempty"`
	SourceRef      string            `json:"source_reference,omitempty"`
	SupersedesID   string            `json:"supersedes_id,omitempty"`
	ConflictGroup  string            `json:"conflict_group_id,omitempty"`
	ValidFrom      *time.Time        `json:"valid_from,omitempty"`
	ValidTo        *time.Time        `json:"valid_to,omitempty"`
	ExpiresAt      *time.Time        `json:"expires_at,omitempty"`
	Revision       int64             `json:"revision"`
	CreatedAt      time.Time         `json:"created_at"`
	UpdatedAt      time.Time         `json:"updated_at"`
	LastAccessedAt *time.Time        `json:"last_accessed_at,omitempty"`
	AccessCount    int64             `json:"access_count"`
}

Entry 是一条结构化长期记忆。结构化字段允许 Repository 先做确定性 去重/冲突判断,content 仍然保留模型可读的原始正文。

type ExtractionInput

type ExtractionInput struct {
	AgentID           string              `json:"agent_id"`
	SessionID         string              `json:"session_id"`
	Scope             Scope               `json:"scope,omitempty"`
	SourceFingerprint string              `json:"source_fingerprint"`
	Messages          []ExtractionMessage `json:"messages"`
	SkipOnly          bool                `json:"skip_only,omitempty"`
	SkippedThrough    int64               `json:"skipped_through,omitempty"`
}

ExtractionInput is a frozen, bounded source for background memory extraction. SourceFingerprint ties any candidate back to the exact compression slice without storing the slice in the memory database.

type ExtractionMessage

type ExtractionMessage struct {
	Role       string               `json:"role"`
	Name       string               `json:"name,omitempty"`
	Content    string               `json:"content,omitempty"`
	ToolCallID string               `json:"tool_call_id,omitempty"`
	ToolCalls  []ExtractionToolCall `json:"tool_calls,omitempty"`
}

ExtractionMessage is the provider-neutral representation of one message passed to a background candidate extractor. It intentionally contains only the frozen compression slice; it is not a session message and must never be written back to a Turn.

type ExtractionToolCall

type ExtractionToolCall struct {
	ID        string `json:"id"`
	Name      string `json:"name"`
	Arguments string `json:"arguments,omitempty"`
}

type Kind

type Kind string
const (
	KindFact       Kind = "fact"
	KindPreference Kind = "preference"
	KindDecision   Kind = "decision"
	KindProcedure  Kind = "procedure"
	KindExperience Kind = "experience"
	KindConstraint Kind = "constraint"
)

type LLMCandidateExtractor

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

LLMCandidateExtractor is an opt-in extractor. It uses the existing text completion abstraction, while CandidatePipeline keeps its call off the active Turn path and bounds the source sent to the model.

func NewLLMCandidateExtractor

func NewLLMCandidateExtractor(client llm.Client, maxCandidates, maxInputChars int) *LLMCandidateExtractor

func (*LLMCandidateExtractor) Extract

func (*LLMCandidateExtractor) ExtractWithUsage

func (e *LLMCandidateExtractor) ExtractWithUsage(ctx context.Context, input ExtractionInput) ([]Candidate, *llm.Usage, error)

ExtractWithUsage is the usage-aware form used by maintenance accounting. Older llm.Client implementations continue to work; their usage is nil.

type LocalService

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

LocalService owns the Agent and global stores for one runtime. The selected scope is mutable at a control-plane boundary, while every Recall call still returns a complete immutable Snapshot for its Turn.

func NewLocalService

func NewLocalService(agent, global *Store, scope Scope) *LocalService

func OpenLocalService

func OpenLocalService(agentPath, globalPath string, scope Scope, identity ...string) (*LocalService, error)

func (*LocalService) AgentID

func (s *LocalService) AgentID() string

func (*LocalService) ApplyMaintenanceOperation

func (s *LocalService) ApplyMaintenanceOperation(ctx context.Context, op MaintenanceOperation, candidates []Candidate, next MaintenanceCursor) ([]WriteResult, error)

ApplyMaintenanceOperation atomically commits the operation record, candidate effects, and cursor. Queue submission alone never advances the cursor.

func (*LocalService) Close

func (s *LocalService) Close() error

func (*LocalService) Consolidate

func (s *LocalService) Consolidate(ctx context.Context, candidates []Candidate) ([]WriteResult, error)

Consolidate serially applies model-inferred candidates. Candidates first enter the database as pending and are promoted only when deterministic conflict checks find no existing contradiction. A contradiction remains a normal pending conflict and therefore still requires the existing typed approval flow; background work never auto-approves it.

func (*LocalService) Forget

func (s *LocalService) Forget(ctx context.Context, scope Scope, id, reason string) (WriteResult, error)

func (*LocalService) Get

func (s *LocalService) Get(ctx context.Context, scope Scope, id string, includeInactive bool) (Entry, error)

func (*LocalService) GetMaintenanceCursor

func (s *LocalService) GetMaintenanceCursor(ctx context.Context) (MaintenanceCursor, error)

GetMaintenanceCursor returns the durable cursor for this service's scope.

func (*LocalService) GetMaintenanceOperation

func (s *LocalService) GetMaintenanceOperation(ctx context.Context, id string) (MaintenanceOperation, error)

GetMaintenanceOperation reads an operation only through the service's private Agent store. It is intentionally independent of the mutable model scope, so a global scope selection cannot widen recovery access.

func (*LocalService) List

func (s *LocalService) List(ctx context.Context, scope Scope, includeInactive bool) ([]Entry, error)

List returns the persisted projection for settings and diagnostics. It is intentionally separate from Recall so inactive entries can be inspected without ever leaking into model context.

func (*LocalService) Maintain

func (s *LocalService) Maintain(ctx context.Context, scope Scope, coreBudget int) (MaintenanceReport, error)

Maintain enforces the deterministic memory housekeeping policy. It is safe to call from a background consolidator: it only changes memory rows and never touches session history or a live Turn snapshot.

func (*LocalService) Recall

func (s *LocalService) Recall(ctx context.Context, req RecallRequest) (Snapshot, error)

func (*LocalService) Remember

func (s *LocalService) Remember(ctx context.Context, req RememberRequest) (WriteResult, error)

func (*LocalService) ReplaceAll

func (s *LocalService) ReplaceAll(ctx context.Context, scope Scope, entries []Entry) error

ReplaceAll is the control-plane write used by the settings API. It deliberately bypasses semantic conflict detection because an explicit settings save is an authoritative replacement of the projection.

func (*LocalService) ResolveConflict

func (s *LocalService) ResolveConflict(ctx context.Context, scope Scope, conflictID string, decision ConflictDecision) (WriteResult, error)

func (*LocalService) Scope

func (s *LocalService) Scope() Scope

Scope reports the scope configured for model-facing operations. It is used only to stamp optional background extraction inputs; it does not grant the extractor access to the other store.

func (*LocalService) Search

func (s *LocalService) Search(ctx context.Context, req SearchRequest) ([]SearchResult, error)

func (*LocalService) SetScope

func (s *LocalService) SetScope(scope Scope)

func (*LocalService) UpdateContent

func (s *LocalService) UpdateContent(ctx context.Context, scope Scope, id, content string) (WriteResult, error)

UpdateContent is the explicit settings-plane edit operation. It keeps the entry identity and revision history while rebuilding the search projection.

type Maintainer

type Maintainer interface {
	Maintain(context.Context, Scope, int) (MaintenanceReport, error)
}

type MaintenanceCursor

type MaintenanceCursor struct {
	AgentID              string    `json:"agent_id"`
	Scope                Scope     `json:"scope"`
	Sequence             int64     `json:"sequence"`
	SourceFingerprint    string    `json:"source_fingerprint"`
	CandidateFingerprint string    `json:"candidate_fingerprint"`
	Revision             int64     `json:"revision"`
	UpdatedAt            time.Time `json:"updated_at"`
}

type MaintenanceOperation

type MaintenanceOperation struct {
	OperationID          string    `json:"operation_id"`
	AgentID              string    `json:"agent_id"`
	Scope                Scope     `json:"scope"`
	SourceFingerprint    string    `json:"source_fingerprint"`
	CandidateFingerprint string    `json:"candidate_fingerprint"`
	ExpectedCursor       int64     `json:"expected_cursor"`
	NextCursor           int64     `json:"next_cursor"`
	Status               string    `json:"status"`
	CreatedAt            time.Time `json:"created_at"`
	UpdatedAt            time.Time `json:"updated_at"`
}

type MaintenanceReport

type MaintenanceReport struct {
	Scope          Scope `json:"scope"`
	CoreBudget     int   `json:"core_budget"`
	DemotedCore    int   `json:"demoted_core"`
	ExpiredEntries int   `json:"expired_entries"`
	Changed        bool  `json:"changed"`
	StoreRevision  int64 `json:"store_revision"`
}

type RecallRequest

type RecallRequest struct {
	AgentID       string
	Scope         Scope
	RootMessageID string
	QueryText     string
	Limit         int
	TokenBudget   int
	CoreBudget    int
	// ContextTokenBudget is the hard budget for the complete rendered
	// memory_context, including its framing and metadata. TokenBudget and
	// CoreBudget remain the per-tier selection budgets.
	ContextTokenBudget int
	IncludeCore        bool
	Now                time.Time
}

type Reference

type Reference struct {
	ID        string    `json:"id"`
	Revision  int64     `json:"revision"`
	Tier      Tier      `json:"tier"`
	Kind      Kind      `json:"kind"`
	Content   string    `json:"content"`
	UpdatedAt time.Time `json:"updated_at"`
}

type RememberRequest

type RememberRequest struct {
	Scope         Scope
	AgentID       string
	Information   string
	Kind          Kind
	Tier          Tier
	SemanticKey   string
	Subject       string
	Predicate     string
	Value         any
	Qualifiers    map[string]string
	Cardinality   string
	Importance    int
	Confidence    int
	Sensitivity   string
	SourceType    string
	SourceSession string
	SourceMessage string
	SourceRef     string
	ValidFrom     *time.Time
	ValidTo       *time.Time
	ExpiresAt     *time.Time
}

type Scope

type Scope string
const (
	ScopeAgent  Scope = "agent"
	ScopeGlobal Scope = "global"
)

type SearchRequest

type SearchRequest struct {
	Scope           Scope
	Query           string
	Limit           int
	IncludeInactive bool
	Now             time.Time
}

type SearchResult

type SearchResult struct {
	Entry Entry   `json:"entry"`
	Score float64 `json:"score,omitempty"`
}

type Service

type Service interface {
	Recall(ctx context.Context, req RecallRequest) (Snapshot, error)
	Search(ctx context.Context, req SearchRequest) ([]SearchResult, error)
	Get(ctx context.Context, scope Scope, id string, includeInactive bool) (Entry, error)
	Remember(ctx context.Context, req RememberRequest) (WriteResult, error)
	Forget(ctx context.Context, scope Scope, id, reason string) (WriteResult, error)
	ResolveConflict(ctx context.Context, scope Scope, conflictID string, decision ConflictDecision) (WriteResult, error)
	SetScope(scope Scope)
}

Service 是 Turn/API 依赖的最小能力集合,由 Workspace SQLite 实现。

type Snapshot

type Snapshot struct {
	ID              string      `json:"id"`
	Scope           Scope       `json:"scope"`
	StoreRevision   int64       `json:"store_revision"`
	RootMessageID   string      `json:"root_message_id"`
	Core            []Reference `json:"core,omitempty"`
	Recalled        []Reference `json:"recalled,omitempty"`
	RenderedContent string      `json:"rendered_content"`
	TokenEstimate   int         `json:"token_estimate"`
	Digest          string      `json:"digest"`
	CreatedAt       time.Time   `json:"created_at"`
}

Snapshot 是一个 Turn 使用的不可变记忆视图。RenderedContent 需要一起 冻结,避免审批恢复或 Node 重启后依据已变化的数据库重新召回出不同上下文。

type Status

type Status string
const (
	StatusActive     Status = "active"
	StatusPending    Status = "pending"
	StatusSuperseded Status = "superseded"
	StatusConflicted Status = "conflicted"
	StatusDeleted    Status = "deleted"
)

type Store

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

Store 是单一 scope 的 Workspace SQLite 权威存储。

func OpenStore

func OpenStore(path string, scope Scope) (*Store, error)

func (*Store) Close

func (s *Store) Close() error

func (*Store) GetMaintenanceCursor

func (s *Store) GetMaintenanceCursor(ctx context.Context, agentID string) (MaintenanceCursor, error)

func (*Store) GetMaintenanceOperation

func (s *Store) GetMaintenanceOperation(ctx context.Context, id string) (MaintenanceOperation, error)

func (*Store) List

func (s *Store) List(ctx context.Context, includeInactive bool) ([]Entry, error)

List is used by the settings API. Model recall must use Recall/Search so it never accidentally injects inactive entries.

type Tier

type Tier string
const (
	TierCore   Tier = "core"
	TierRecall Tier = "recall"
)

type WriteOutcome

type WriteOutcome string
const (
	WriteAdded           WriteOutcome = "added"
	WriteDuplicate       WriteOutcome = "duplicate"
	WriteSuperseded      WriteOutcome = "superseded"
	WritePendingConflict WriteOutcome = "pending_conflict"
)

type WriteResult

type WriteResult struct {
	Outcome       WriteOutcome `json:"outcome"`
	Entry         *Entry       `json:"entry,omitempty"`
	ExistingID    string       `json:"existing_id,omitempty"`
	Superseded    []string     `json:"superseded,omitempty"`
	Conflict      *Conflict    `json:"conflict,omitempty"`
	StoreRevision int64        `json:"store_revision"`
}

Jump to

Keyboard shortcuts

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