memory

package
v1.14.0 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2026 License: Apache-2.0 Imports: 11 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 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 ConfigSnapshot

type ConfigSnapshot struct {
	Driver             string
	DSN                string
	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