Help us shape the future of Go! Take the 2026 Go Developer Survey

memory

package
v1.30.12 Latest Latest
Warning

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

Go to latest
Published: Aug 29, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package memory owns Harbor's declared-policy, identity-scoped, pluggable memory subsystem.

lands the leaf surface:

  • The single mandatory `MemoryStore` interface every backend (inmem here, sqlite + postgres) implements.
  • The shared types — `Strategy`, `Health`, `ConversationTurn`, `TrajectoryDigest`, `LLMContextPatch`, `Snapshot`.
  • Sentinel errors compared via `errors.Is`.
  • The §4.4 extensibility-seam plumbing (registry + factory).
  • Ctx helpers (`WithStore` / `MustFrom` / `From`).

The interface owns the typed shape; drivers persist opaque bytes through `state.StateStore` via the typed wrapper pattern. Memory records key on `(identity.Quadruple, Kind= "memory.state")` — sessions own the wrapper layer of session records, memory owns its own.

Identity is mandatory at every method. The triple `(tenant, user, session)` MUST be fully populated; empty `RunID` is accepted (memory is session-scoped, not run-scoped, mirroring the `state.StateStore` rule). Missing-triple operations fail closed with `ErrIdentityRequired` AND emit a `memory.identity_rejected` event on the configured `events.EventBus` so the rejection is observable — never silent (per AGENTS.md §5 "Fail loudly").

Harbor ships `Strategy = StrategyNone` only:

  • `AddTurn` is a no-op.
  • `GetLLMContext` returns an empty patch.
  • `EstimateTokens` returns 0.
  • `Flush` is a no-op.
  • `Health` returns `HealthHealthy`.
  • `Snapshot` returns an empty snapshot.
  • `Restore` accepts only an empty snapshot; non-empty is `ErrInvalidSnapshot`.

A later phase will activate `StrategyTruncation` and `StrategyRollingSummary`; A later phase will add the SQLite + Postgres drivers under the same conformance suite.

Index

Constants

View Source
const DefaultDriver = "inmem"

DefaultDriver is the production driver name. The SQL drivers (SQLite + Postgres) registers additional names.

View Source
const DefaultSemanticTopK = 5

DefaultSemanticTopK is the `SearchTurns` result cap applied when the caller passes no positive limit and the config carries no `RetrievalTopK`.

View Source
const EventTypeMemoryCallerBlockAdmitted events.EventType = "memory.caller_block_admitted"

EventTypeMemoryCallerBlockAdmitted is emitted once per run that admits caller-supplied content into its read-only external-memory tier. A Console that cannot tell caller-asserted memory from runtime-retrieved memory can audit neither, so the admission is announced over the Protocol.

It carries a SIZE and never CONTENT — see CallerBlockAdmittedPayload.

It fires at admission, which happens before planning, so the event lands whether or not the run subsequently succeeds.

View Source
const EventTypeMemoryHealthChanged events.EventType = "memory.health_changed"

EventTypeMemoryHealthChanged is emitted on every `Health` FSM transition under `rolling_summary`. Subscribers (Console, audit pipeline) render the transition; SREs alert on `degraded` duration. RFC §6.6.

The observable health-transition emit is the explicit exception to AGENTS.md §13's "no silent degradation" rule — degraded mode IS the observable failure path, and the emit makes it observable (and therefore not silent).

View Source
const EventTypeMemoryIdentityRejected events.EventType = "memory.identity_rejected"

EventTypeMemoryIdentityRejected is emitted on the events bus when a `MemoryStore` method is called with a missing identity triple (fail-closed contract). The store ALSO returns `ErrIdentityRequired`; the bus emit makes the rejection observable from the Console / audit pipeline.

Registered via `events.RegisterEventType` from this package's `init()` so `Publish` accepts the type without `ErrUnknownEventType`.

View Source
const EventTypeMemoryItemDeleted events.EventType = "memory.item_deleted"

EventTypeMemoryItemDeleted is emitted on the events bus when an admin evicts a memory turn through the `memory.delete` Protocol method ( ). The audit trail for the eviction. SafePayload — the hashed key only, never any record value bytes.

View Source
const EventTypeMemoryItemPut events.EventType = "memory.item_put"

EventTypeMemoryItemPut is emitted on the events bus when an admin adds a memory turn through the `memory.put` Protocol method It is the audit trail for the mutation: the Console / audit pipeline observes who added what (by key) and when. SafePayload by construction — it carries the deterministic (hashed) turn key only, never the operator-supplied turn text.

View Source
const EventTypeMemoryRecoveryDropped events.EventType = "memory.recovery_dropped"

EventTypeMemoryRecoveryDropped is emitted when the `rolling_summary` recovery backlog overflows `RecoveryBacklogMax` and the executor drops the oldest queued batch to make room. Per the bounded recovery loop.

View Source
const KindMemoryState = "memory.state"

KindMemoryState is the canonical record-kind string Harbor uses to route memory state in the persistence layer. Since the strategy work every driver — InMem, SQLite, Postgres — persists this kind through the injected `state.StateStore` via the shared strategy executor; the SQL drivers no longer write a private `memory_state` table on the strategy path. The constant is exported so cross-driver tests and operators can reference it.

Variables

View Source
var (
	// ErrNotFound — a load-by-key style lookup found nothing.
	// Harbor returns this when `Snapshot` is asked for a slot
	// the StateStore wrapper layer has never written.
	ErrNotFound = errors.New("memory: record not found")

	// ErrIdentityRequired — a method was called with a
	// `Quadruple` whose tenant, user, or session was empty.
	// The fail-closed gate
	ErrIdentityRequired = errors.New("memory: identity triple incomplete")

	// ErrUnknownDriver — `Open` was asked for a driver name no
	// registered factory handles. The wrapped message lists the
	// registered names.
	ErrUnknownDriver = errors.New("memory: unknown driver")

	// ErrStoreClosed — a method was called after `Close`.
	ErrStoreClosed = errors.New("memory: store is closed")

	// ErrStrategyNotImplemented — `Open` (or a driver) was asked for
	// an UNKNOWN strategy name. The three canonical strategies
	// (`none` / `truncation` / `rolling_summary`) are implemented on
	// every driver via the shared strategy executor;
	// this sentinel now guards an unrecognised strategy string, not a
	// phase gap. (The error text is preserved for callers that match it.)
	ErrStrategyNotImplemented = errors.New("memory: strategy not implemented at this phase")

	// ErrInvalidSnapshot — `Restore` was called with a snapshot
	// whose Strategy mismatches the driver's, or with non-empty
	// bytes against a `StrategyNone` driver. Fail loudly; never
	// silently coerce.
	ErrInvalidSnapshot = errors.New("memory: invalid snapshot for this strategy")

	// ErrSemanticDisabled — `SearchTurns` was called on a store
	// whose retrieval mode is not `semantic`. Fail loudly: a
	// similarity search against a store that never embedded
	// anything would silently return nothing useful. Enable with
	// `Retrieval: RetrievalSemantic` + `Deps.Embedder`.
	ErrSemanticDisabled = errors.New("memory: semantic retrieval not enabled (set retrieval mode \"semantic\" and provide Deps.Embedder)")

	// ErrInvalidHealthTransition — a strategy executor attempted a
	// `Health` transition outside the documented FSM
	// (`healthy ↔ retry ↔ degraded ↔ recovering`). Fail loudly: an
	// invalid transition is a programming error, not a recoverable
	// state.
	ErrInvalidHealthTransition = errors.New("memory: invalid health transition")
)

Sentinel errors. Callers compare via `errors.Is`.

Functions

func EmitHealthChanged

func EmitHealthChanged(ctx context.Context, bus events.EventBus, id identity.Quadruple, prior, next Health, reason string) error

EmitHealthChanged publishes the `memory.health_changed` event on `bus`. The transition is validated against the documented `Health` FSM (see `ValidateHealthTransition`); an invalid transition returns wrapped `ErrInvalidHealthTransition` and does NOT publish — fail loudly, never silently coerce an illegal transition into a silent no-op (AGENTS.md §13).

The `reason` string is a short static label ("summarizer_failed", "retries_exhausted", "recovery_loop_drained", etc.) that subscribers / SREs can filter on. It MUST NOT carry caller-controlled bytes.

Identity is required — the event's `Identity` field carries the executor's per-key triple so subscribers can scope the transition to a session.

Self-loops (prior == next) ARE valid transitions per `ValidateHealthTransition`; this helper still emits the event so downstream observability can count "stayed healthy across summarisation success" — but it is the caller's choice to skip the self-loop emit if the cost is undesirable.

func EmitIdentityRejected

func EmitIdentityRejected(ctx context.Context, bus events.EventBus, q identity.Quadruple, operation string) error

EmitIdentityRejected publishes the `memory.identity_rejected` event on `bus` and returns the wrapped sentinel error. Drivers call this from every method that detected an identity-rejection so the audit emit path is consistent across implementations.

The bus's `ValidateEvent` requires a fully-populated identity triple; the partial / empty triple the caller supplied is preserved where present and substituted with the `missingIdentitySentinel` string elsewhere, so the rejection event itself is bus-publishable. The audit-visible payload's `Reason` field names the component(s) that were actually missing.

`operation` is the rejected method name ("AddTurn", "GetLLMContext", etc.). The reason string is computed from whichever components were missing on the supplied quadruple.

Returns the wrapped `ErrIdentityRequired` for the caller to surface. If Publish fails, the wrapped error names both the rejection cause and the bus failure — callers MUST NOT silently drop either.

func EmitRecoveryDropped

func EmitRecoveryDropped(ctx context.Context, bus events.EventBus, id identity.Quadruple, reason string) error

EmitRecoveryDropped publishes the `memory.recovery_dropped` event on `bus`. Used by the `rolling_summary` recovery loop when the backlog overflows `RecoveryBacklogMax` and the oldest queued batch is dropped to make room (bounded recovery loop).

Identity is required.

func Register

func Register(name string, factory Factory)

Register installs a driver factory under `name`. Drivers self- register from their package `init()`; `cmd/harbor` blank-imports the production driver to trigger registration. Per AGENTS.md §4.4.

Re-registering the same name panics — the registration model is write-once-at-init and a duplicate signals a build mis-config.

func RegisteredDrivers

func RegisteredDrivers() []string

RegisteredDrivers returns a sorted list of driver names. Useful for boot-log emission ("memory drivers available: inmem") and for surfacing in error messages.

func ValidateHealthTransition

func ValidateHealthTransition(prior, next Health) error

ValidateHealthTransition returns nil when `(prior → next)` is a legal `Health` FSM edge. Invalid transitions return wrapped `ErrInvalidHealthTransition` — fail loudly per AGENTS.md §5; an invalid transition is a programming error in the calling executor, not a recoverable state.

The empty `Health{}` (zero value) is treated as `HealthHealthy` for both sides — a freshly-constructed executor implicitly starts healthy.

func ValidateIdentity

func ValidateIdentity(q identity.Quadruple) error

ValidateIdentity returns wrapped `ErrIdentityRequired` when any of (tenant, user, session) is empty. Empty `RunID` is acceptable. Drivers call this at the boundary before any I/O.

func WithStore

func WithStore(ctx context.Context, store MemoryStore) context.Context

WithStore attaches the store to ctx for downstream handlers.

Types

type CallerBlockAdmittedPayload added in v1.25.0

type CallerBlockAdmittedPayload struct {
	events.SafeSealed `json:"-"`

	// Bytes is the size of the admitted document as it arrived on the
	// wire. It is the only caller-influenced value on the payload.
	Bytes int `json:"bytes"`
	// Tier is the prompt tier the content was admitted into — the
	// runtime-owned wrapper name, always the read-only external-memory
	// tier.
	Tier string `json:"tier"`
	// Key is the fixed runtime-owned map key the content was composed
	// under within that tier.
	Key string `json:"key"`
}

CallerBlockAdmittedPayload is the audit payload for EventTypeMemoryCallerBlockAdmitted. SafePayload BY CONSTRUCTION, and the construction is the point: the only caller-derived quantity on it is a byte COUNT. `Tier` and `Key` are runtime-owned constants, not caller input — the caller names no key.

No fragment of the admitted content appears here, and none may ever be added: the payload is caller-controlled bytes that would require redaction, and the audit trail records THAT an admission happened and how large it was, never what it said (CLAUDE.md §7 rules 6-7).

type ConfigSnapshot

type ConfigSnapshot struct {
	Driver             string
	DSN                string
	MigrationMode      sqlmigrate.Mode
	Strategy           Strategy
	BudgetTokens       int
	RecoveryBacklogMax int
	RecentTurns        int
	Retrieval          RetrievalMode
	RetrievalTopK      int
}

ConfigSnapshot is the strict subset of `config.MemoryConfig` the memory package consumes. Keeping a snapshot decouples drivers from the config package's type evolution. Callers (typically `cmd/harbor/main.go`'s bootstrap or a test wiring helper) translate `config.MemoryConfig` → `ConfigSnapshot` at the seam.

`DSN` is consumed by the SQLite + Postgres drivers; the InMem driver ignores it. Validation of "DSN required for persistent drivers" lives at the config layer (`validateMemory` in `internal/config/validate.go`) and at the driver constructor itself — fail-loudly twice so a misconfiguration surfaces early.

`RecoveryBacklogMax` is consumed by the `rolling_summary` strategy executor only; other strategies ignore the field. Default (zero) → strategy.DefaultRecoveryBacklogMax. `Retrieval` opts in to a retrieval mode layered ON TOP of the strategy (the default keeps strategy-shaped retrieval only; `RetrievalSemantic` additionally serves `SearchTurns`). `RetrievalTopK` caps semantic results when the caller passes no limit; zero → `DefaultSemanticTopK`. Both are ignored by the default mode.

`RecentTurns` is the verbatim recent-window size for the `rolling_summary` strategy. Zero selects the strategy default (`strategy.FullZoneTurns`); positive values override it. Ignored by `none` and `truncation`.

func SnapshotFromConfig added in v1.3.0

func SnapshotFromConfig(cfg config.MemoryConfig) ConfigSnapshot

SnapshotFromConfig projects the operator-facing `config.MemoryConfig` block onto the memory package's decoupled ConfigSnapshot. Every config field maps 1:1; the field-parity test in from_config_test.go fails the build when a new config field lands without a projection (or an explicit exclusion naming why).

type ConversationTurn

type ConversationTurn struct {
	UserMessage         string
	AssistantResponse   string
	TrajectoryDigest    *TrajectoryDigest
	ArtifactsShown      map[string]any
	ArtifactsHiddenRefs []string
	Timestamp           time.Time
}

ConversationTurn is one turn of a memory-tracked conversation. Producers (the planner runtime) hand turns to `AddTurn`.

`ArtifactsShown` / `ArtifactsHiddenRefs` carry the model-visible / model-hidden artifact references for this turn so memory's downstream injection logic can prune or include them per the configured strategy. Snapshot/Restore round-trips both fields through the Snapshot bytes but applies no strategy logic.

type Deps

type Deps struct {
	State      state.StateStore
	Bus        events.EventBus
	Summarizer Summarizer
	Embedder   Embedder
}

Deps carries the runtime dependencies a memory driver needs.

The `State` field is mandatory (typed wrapper writes opaque bytes through the generic surface). The `Bus` field is mandatory so identity-rejection emits land on the audit pipeline. Drivers MUST NOT accept missing deps silently; the registry rejects an `Open` call whose Deps omits either with a wrapped error.

The `Summarizer` field is the injectable LLM-edge callable the `rolling_summary` strategy consumes. It is OPTIONAL — required only when `cfg.Strategy == StrategyRollingSummary`, ignored by `none` / `truncation`. The registry routes it into the driver factory, which threads it into the strategy executor. A `rolling_summary` config without a `Summarizer` fails loudly at `Open` (mirroring `strategy.New`'s rejection) — never a stub fallback (AGENTS.md §13). Existing callers that construct `Deps{State, Bus}` keep compiling: the zero value is nil, valid for the non-summarising strategies. The `Embedder` field is the injectable text→vector callable the `semantic` retrieval mode consumes. It is OPTIONAL — required only when `cfg.Retrieval == RetrievalSemantic`, ignored otherwise. A semantic config without an Embedder fails loudly at `Open` (mirroring the Summarizer rule) — never a stub fallback (AGENTS.md §13). Existing callers that construct `Deps{State, Bus}` keep compiling: the zero value is nil, valid for the default retrieval mode.

type Embedder added in v1.4.0

type Embedder interface {
	Embed(ctx context.Context, texts []string) ([][]float32, error)
}

Embedder is the injectable text→vector callable the semantic retrieval mode consumes — the same consumer-side-interface shape as `Summarizer`. The canonical implementation is constructed via the embeddings factory (`internal/embeddings`); any `embeddings.Embedder` satisfies this interface.

Concurrent-reuse contract: one `Embedder` instance is safe to share across N concurrent goroutines. Implementers MUST honour `ctx.Done()`.

type Factory

type Factory func(cfg ConfigSnapshot, deps Deps) (MemoryStore, error)

Factory builds a `MemoryStore` from a `ConfigSnapshot` + `Deps`. Drivers expose one `Factory` each via `init()` → `Register`.

type Health

type Health string

Health enumerates the memory subsystem health states.

only produces `HealthHealthy`. A later phase will drive the full FSM (`healthy → retry → degraded → recovering → healthy`) for `rolling_summary` failures.

const (
	// HealthHealthy — operating normally.
	HealthHealthy Health = "healthy"
	// HealthRetry — last summarisation attempt failed; will retry
	// next opportunity. Reserved for a later phase.
	HealthRetry Health = "retry"
	// HealthDegraded — retry budget exhausted; falling back to
	// truncation semantics and queueing recovery. Reserved for
	HealthDegraded Health = "degraded"
	// HealthRecovering — recovery loop is running; will return to
	// healthy on success. Reserved for a later phase.
	HealthRecovering Health = "recovering"
)

Health values.

type HealthChangedPayload

type HealthChangedPayload struct {
	events.SafeSealed
	PriorHealth Health
	NewHealth   Health
	Reason      string
}

HealthChangedPayload reports a `Health` FSM transition. SafePayload by construction — `PriorHealth` + `NewHealth` are bounded enumerable strings; `Reason` is a short static string indicating the transition cause ("summarizer_failed", "retries_exhausted", "recovery_loop_drained", etc.). No caller-controlled bytes survive on the payload.

Subscribers MAY admin-scope-filter to fan-in cross-tenant health transitions for fleet-level alerting.

type LLMContextPatch

type LLMContextPatch struct {
	Strategy    Strategy
	Summary     string
	RecentTurns []ConversationTurn
	Tokens      int
}

LLMContextPatch is the output `GetLLMContext` returns: the patch a planner runtime applies to its LLM call.

Strategy=none returns an empty patch; later strategies return a rolling summary text, ordered recent turns, and a token estimate the planner can compare against its context-window budget.

type MemoryIdentityRejectedPayload

type MemoryIdentityRejectedPayload struct {
	events.SafeSealed
	Operation string
	Reason    string
}

MemoryIdentityRejectedPayload reports a missing-identity rejection. SafePayload by construction — both fields are bounded enumerable strings (the operation name + a static reason); no caller-controlled bytes survive on the payload.

`Operation` is the rejected method name ("AddTurn", "GetLLMContext", etc.). `Reason` is a short static string indicating which component was missing ("tenant_id empty" / "user_id empty" / "session_id empty" / "tenant_id and user_id empty", etc.).

The Event's `Identity` field carries whatever the caller supplied (zeroed or partial); the bus's `ValidateEvent` would normally reject empty-triple events, so the bus publisher substitutes the missing components with a `"<missing>"` sentinel so the rejection event itself is bus-publishable. Subscribers MAY admin-scope- filter to fan-in cross-tenant rejections.

type MemoryMutationPayload added in v1.3.0

type MemoryMutationPayload struct {
	events.SafeSealed
	Operation string
	Key       string
}

MemoryMutationPayload is the audit payload for the `memory.item_put` / `memory.item_deleted` events. SafePayload by construction: `Operation` is a bounded enumerable string ("put" / "delete"); `Key` is the deterministic content-addressed turn key (a sha256 prefix), never operator-supplied bytes. The record value text is NEVER carried here — it is caller-controlled and would require redaction; the audit trail records THAT a mutation happened, by key, not its bytes.

type MemoryStore

type MemoryStore interface {
	// AddTurn appends a conversation turn to the memory tracked
	// for `id`. Strategy=none is a no-op (returns nil); other
	// strategies will apply their shape logic.
	AddTurn(ctx context.Context, id identity.Quadruple, turn ConversationTurn) error

	// GetLLMContext returns the patch a planner runtime applies to
	// its LLM call. Strategy=none returns the zero value of
	// `LLMContextPatch`.
	GetLLMContext(ctx context.Context, id identity.Quadruple) (LLMContextPatch, error)

	// EstimateTokens returns the token-estimate for the memory
	// payload `GetLLMContext` would inject right now. Strategy=
	// none returns 0.
	EstimateTokens(ctx context.Context, id identity.Quadruple) (int, error)

	// Flush drops every in-flight turn and resets the memory for
	// `id` to a clean state. Strategy=none is a no-op.
	Flush(ctx context.Context, id identity.Quadruple) error

	// Health reports the current health state for `id`'s memory.
	// Strategy=none always reports `HealthHealthy`.
	Health(ctx context.Context, id identity.Quadruple) (Health, error)

	// Snapshot exports a portable snapshot of `id`'s memory state.
	// Strategy=none returns an empty `Snapshot{Strategy: StrategyNone}`.
	Snapshot(ctx context.Context, id identity.Quadruple) (Snapshot, error)

	// SearchTurns returns up to `limit` stored turns ranked by
	// embedding similarity to `query`, identity-scoped — retrieval
	// never crosses the `(tenant, user, session)` boundary. Only
	// the semantic retrieval mode serves it; every other mode fails
	// loudly with `ErrSemanticDisabled` (never an empty success).
	// `limit <= 0` falls back to the configured `RetrievalTopK`
	// (default `DefaultSemanticTopK`). An empty query is an error.
	SearchTurns(ctx context.Context, id identity.Quadruple, query string, limit int) ([]ScoredTurn, error)

	// Restore imports a previously-captured `Snapshot`. The
	// Snapshot's Strategy MUST match the driver's configured
	// Strategy; mismatched strategies (e.g. restoring a
	// `truncation` snapshot into a `none` store) returns
	// `ErrInvalidSnapshot`. Strategy=none accepts only empty
	// snapshots; non-empty Bytes returns `ErrInvalidSnapshot`.
	Restore(ctx context.Context, id identity.Quadruple, snap Snapshot) error

	// Close releases driver resources. Idempotent. After Close,
	// every method returns `ErrStoreClosed`.
	Close(ctx context.Context) error
}

MemoryStore is Harbor's mandatory memory interface. A single surface; every V1 driver (inmem here, sqlite + postgres at Postgres) implements every method. No `Supports*` ceremony per AGENTS.md §4.4.

Identity-mandatory contract:

  • Every method validates the identity `Quadruple` at the boundary. Empty tenant / user / session returns wrapped `ErrIdentityRequired` AND emits one `memory.identity_rejected` event on the bus. Empty `RunID` is accepted (memory is session-scoped).

Concurrent-reuse contract:

  • One instance is safe to share across N concurrent goroutines. Mutable state is internally synchronised; per- call state lives in `ctx` and the supplied `Quadruple`, never on the driver.

func From

func From(ctx context.Context) (MemoryStore, bool)

From returns the `MemoryStore` in ctx and a presence bool. Use when absence is recoverable.

func MustFrom

func MustFrom(ctx context.Context) MemoryStore

MustFrom returns the `MemoryStore` in ctx; panics with `ErrStoreClosed` (used as the sentinel for "no store configured") when none is present. Use in handler/runtime paths where a store is mandatory.

func Open

func Open(_ context.Context, cfg ConfigSnapshot, deps Deps) (MemoryStore, error)

Open returns the `MemoryStore` built by the factory whose name matches `cfg.Driver` (defaults to `DefaultDriver` when empty).

Deps are validated: a missing StateStore or EventBus returns a wrapped error before the factory runs — fail loudly, never silently degrade.

func OpenDriver

func OpenDriver(name string, cfg ConfigSnapshot, deps Deps) (MemoryStore, error)

OpenDriver opens a specific driver by name; useful for tests that want to exercise the registry against a non-default driver.

type OverflowPolicy

type OverflowPolicy string

OverflowPolicy is the buffer-overflow action a `truncation`-style strategy applies when the recent-window buffer's token total exceeds the configured `BudgetTokens`. Harbor ships only `OverflowDropOldest`. The original trio `truncate_oldest | truncate_summary | error` was narrowed to a single safe default; the `error` policy is a silent- degradation footgun and `truncate_summary` conflates strategies).

const (
	// OverflowDropOldest evicts oldest turns until the buffer's
	// token estimate fits within the budget. The only shipped
	// policy.
	OverflowDropOldest OverflowPolicy = "drop_oldest"
)

type RecallSettings added in v1.4.0

type RecallSettings struct {
	// Enabled is true when memory.retrieval is "semantic". When false,
	// SearchTurns is never called and the prompt is unchanged.
	Enabled bool
	// TopK is the maximum number of recalled turns to inject. 0 falls
	// back to the memory subsystem default (DefaultSemanticTopK = 5).
	TopK int
	// MinScore is the cosine-similarity floor. Turns scoring below
	// this value are not injected. Valid range [-1, 1]; default 0.0.
	MinScore float64
}

RecallSettings carries the run-loop recall parameters the FetchMemoryBlocks helper reads. It is separate from ConfigSnapshot so a headless caller that builds its own RunSpec can supply recall settings without constructing a full config.MemoryConfig.

The zero value is valid: Enabled=false means semantic recall is off and SearchTurns is never called.

func RecallFromConfig added in v1.4.0

func RecallFromConfig(cfg config.MemoryConfig) RecallSettings

RecallFromConfig projects the operator-facing `config.MemoryConfig` block onto a RecallSettings value the run-loop memory step consumes. The field-parity test in from_config_test.go ensures every recall-relevant config field is either projected here or explicitly excluded with a reason, closing the silent-field-drop drift class.

type Record

type Record struct {
	Strategy Strategy           `json:"strategy"`
	Turns    []ConversationTurn `json:"turns,omitempty"`
	// Summary is the rolling-summary text the `rolling_summary` strategy
	// folds compacted turns into. Added in so a
	// Snapshot → drop-turn → Restore read-modify-write (the `memory.delete`
	// mutation) round-trips the summary LOSSLESSLY rather than dropping it.
	// The json tag matches the strategy's persisted `memoryStateRecord`
	// (`internal/memory/strategy`), so decoding a real snapshot into this
	// envelope and re-marshalling preserves every field. `omitempty` keeps
	// it byte-compatible with pre-108n records that never wrote a summary.
	Summary string `json:"summary,omitempty"`
}

Record is the JSON envelope every driver persists as the opaque `Snapshot.Bytes` payload. The shape is exported so tests + later driver implementations share a single source of truth (wire envelope centralised in the `memory` package for cross-driver byte-stable Snapshot/Restore).

only writes empty records (Strategy=none has no mutations). A later phase will populate `Turns` for the truncation + rolling_summary strategies. The struct's JSON tags pin the wire format and MUST NOT change after An earlier phase merged — additive fields are fine (later strategies will append fields), but renaming an existing field would break cross-driver round-trip.

type RecoveryDroppedPayload

type RecoveryDroppedPayload struct {
	events.SafeSealed
	Reason string
}

RecoveryDroppedPayload reports a recovery-backlog overflow drop. SafePayload by construction — `Reason` is a short static string ("backlog_overflow"); no caller-controlled bytes.

type RetrievalMode added in v1.4.0

type RetrievalMode string

RetrievalMode declares the opt-in retrieval shape layered on top of the strategy. The mode COMPOSES with the configured `Strategy` — `rolling_summary` keeps its summary + recent-turn patch unchanged; a semantic mode additionally serves similarity search over embedded turns via `SearchTurns`.

const (
	// RetrievalDefault — the zero value. Retrieval is shaped by the
	// strategy alone; `SearchTurns` fails loudly with
	// `ErrSemanticDisabled`.
	RetrievalDefault RetrievalMode = ""
	// RetrievalSemantic — turns are embedded at `AddTurn` (vectors
	// persist alongside the memory records through the same
	// StateStore floor, identity-scoped) and `SearchTurns` ranks by
	// cosine similarity. Requires `Deps.Embedder` — no stub
	// fallback.
	RetrievalSemantic RetrievalMode = "semantic"
)

RetrievalMode values.

type ScoredTurn added in v1.4.0

type ScoredTurn struct {
	Turn  ConversationTurn
	Score float64
}

ScoredTurn is one `SearchTurns` result: a stored conversation turn plus its similarity score (cosine, in [-1, 1], higher is closer).

type Snapshot

type Snapshot struct {
	Strategy Strategy
	Bytes    []byte
}

Snapshot is the export shape for `Snapshot` / `Restore`. The Strategy field round-trips so a `Restore` against a driver configured for a different Strategy fails loudly.

`Bytes` is opaque to callers; only a driver of the same Strategy can `Restore` them. Crossing driver boundaries (e.g. inmem snapshot → sqlite restore) is safe because the bytes are JSON-serialised internal records, not driver-private structures.

func (Snapshot) IsEmpty

func (s Snapshot) IsEmpty() bool

IsEmpty reports whether the snapshot is operationally empty (no strategy + no bytes). Used by `Restore` to accept the trivial-snapshot round-trip under Strategy=none.

type Strategy

type Strategy string

Strategy declares the memory shape the store applies.

Harbor ships `StrategyNone` operational. `StrategyTruncation` and `StrategyRollingSummary` are declared so operators can stage their config today; the registry's `Open` rejects them with `ErrStrategyNotImplemented` until a later phase lands.

const (
	// StrategyNone is the no-op memory shape. AddTurn is a no-op;
	// GetLLMContext returns an empty patch. Operationally the
	// "memory disabled" mode.
	StrategyNone Strategy = "none"
	// StrategyTruncation keeps a recent-turn window with budget
	// enforcement. Reserved for a later phase.
	StrategyTruncation Strategy = "truncation"
	// StrategyRollingSummary keeps a recent-turn window plus a
	// background-summarised long-term context. Reserved for a later phase.
	StrategyRollingSummary Strategy = "rolling_summary"
)

Strategy values.

type SummarizeRequest

type SummarizeRequest struct {
	PreviousSummary string
	Turns           []ConversationTurn
}

SummarizeRequest carries the summariser inputs. `PreviousSummary` is the prior rolling summary (empty on the first turn); `Turns` is the batch of recently-evicted turns to fold into the summary.

type SummarizeResponse

type SummarizeResponse struct {
	Summary string
}

SummarizeResponse carries the summariser output.

type Summarizer

type Summarizer interface {
	Summarize(ctx context.Context, id identity.Quadruple, req SummarizeRequest) (SummarizeResponse, error)
}

Summarizer is the injectable callable the `rolling_summary` strategy consumes. The LLM-backed implementation lands +; Harbor ships only the interface and a test-grade stub (`EchoSummarizer`, exported from `internal/memory/strategy`).

The interface intentionally mirrors the "input `{previous_summary, turns}`, output `{summary: string}`" with a Go-idiomatic `(ctx, identity, req)` shape so the LLM-client integration phase doesn't have to invent a fresh shape.

Concurrent-reuse contract: one `Summarizer` instance is safe to share across N concurrent goroutines. Implementers MUST honour `ctx.Done()`; the executor cancels in-flight summaries on `Close`.

type TrajectoryDigest

type TrajectoryDigest struct {
	ToolsInvoked        []string
	ObservationsSummary string
	ReasoningSummary    string
	ArtifactsRefs       []string
}

TrajectoryDigest is the compact planner-side trace snapshot the memory subsystem MAY persist alongside the turn. Harbor does not ingest it (Strategy=none); the type ships now so the strategy layer and downstream planner phases share one definition.

Directories

Path Synopsis
Package conformancetest exposes the canonical correctness suite every `memory.MemoryStore` driver must pass.
Package conformancetest exposes the canonical correctness suite every `memory.MemoryStore` driver must pass.
drivers
inmem
Package inmem is Harbor's V1 in-memory MemoryStore driver.
Package inmem is Harbor's V1 in-memory MemoryStore driver.
postgres
Package postgres is Harbor's Postgres-backed `memory.MemoryStore` driver.
Package postgres is Harbor's Postgres-backed `memory.MemoryStore` driver.
sqlite
Package sqlite is Harbor's SQLite-backed `memory.MemoryStore` driver.
Package sqlite is Harbor's SQLite-backed `memory.MemoryStore` driver.
Package protocol — additions: the strategy-trace read projection + the admin-gated mutation pair (`memory.put` / `memory.delete`).
Package protocol — additions: the strategy-trace read projection + the admin-gated mutation pair (`memory.put` / `memory.delete`).
Package strategy holds the memory-strategy executors: the algorithmic core that any `memory.MemoryStore` driver delegates to.
Package strategy holds the memory-strategy executors: the algorithmic core that any `memory.MemoryStore` driver delegates to.

Jump to

Keyboard shortcuts

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