Documentation
¶
Overview ¶
Package protocol — Phase 108n (D-186) additions: the strategy-trace read projection + the admin-gated mutation pair (`memory.put` / `memory.delete`). Like List / Get / Health these are stateless pure functions — every dependency is passed in per call. They compose ONLY the shipped `memory.MemoryStore` interface (Snapshot / Restore / AddTurn / GetLLMContext / Health) — no new driver-seam method, so all three V1 drivers (inmem / sqlite / postgres) back them with zero per-driver work.
strategy_trace is an honest read ¶
`StrategyTrace` projects the strategy's LIVE `GetLLMContext` + `Health` output — the rolling-summary text the strategy injects, the verbatim-turn count it keeps, the token estimate, and the health state. It is NOT a fabricated per-step "selection with rejections" (the rolling_summary strategy summarises; it does not select-and-reject candidates). An empty session projects an empty trace (CLAUDE.md §13 — no synthesised data).
The mutations are read-modify-write on the shipped interface ¶
`Put` appends a turn via `AddTurn`. `Delete` removes ONE turn by key via a `Snapshot` → drop-the-keyed-turn → `Restore` round-trip — the Record envelope (incl. the rolling summary) is preserved losslessly (the `memory.Record.Summary` field added in 108n). Both emit an audit event on the bus (`memory.item_put` / `memory.item_deleted`, SafePayload — the hashed key only, never the turn text). Admin-gating lives at the handler edge (D-079); the service trusts a gated, identity-validated call.
Package protocol composes the read-only Console-memory-page Protocol surface (Phase 73j / D-118) on top of the shipped memory subsystem.
It exposes three pure functions — List, Get, Health — that the Protocol stream-transport handlers (`internal/protocol/transports/ stream/memory_handler.go`) call to answer the `memory.list` / `memory.get` / `memory.health` methods. The functions are stateless: every dependency is passed in per call, nothing is cached on a package-level value, and the compiled artifacts they consume (MemoryStore, ArtifactStore, the events Aggregator) are themselves D-025-safe.
The projection model ¶
The shipped `memory.MemoryStore` interface (Phases 23–25) is per-identity: it has no per-item enumeration method. It exposes `Snapshot(ctx, id)` — an opaque JSON `memory.Record{Strategy, Turns}` — and `Health(ctx, id)`. This package projects that record into the Console-page row shape: each conversation turn in a snapshot becomes one `MemoryItem` row, keyed by a deterministic per-turn key (`memTurnKey`). The rolling-summary text, when present, is NOT a separate row — it is folded into the strategy metadata. This is the honest projection: the runtime's memory state is conversation turns, and the Memory page renders them per-identity.
Identity is mandatory (D-001 / D-033) ¶
Every function validates the identity quadruple before touching the store. A missing tenant / user / session fails loudly with `memory.ErrIdentityRequired`; the caller (the stream handler) maps that onto the canonical `CodeIdentityRequired` Protocol error. The driver layer ALSO emits a `memory.identity_rejected` event on the bus (D-033) — this package does not re-emit; it relies on the shipped driver-layer emit and never masks the rejection.
Heavy values bypass via artifacts (D-026) ¶
`Get` mirrors the LLM-edge enforcement pass (`internal/llm/safety.go`): a record value whose byte length meets or exceeds the configured heavy-content threshold is routed through the ArtifactStore and the detail ships a `MemoryArtifactRef` instead of inline bytes. A driver that hands back raw heavy bytes that this package would otherwise inline is a leak — `Get` fails loudly with `ErrContextLeak` rather than inlining (the same posture the LLM edge takes).
No mutation surface ¶
V1 is read-only. `memory.put` / `memory.delete` are deferred to Phase 73 / post-V1 (page-memory.md §10); this package ships no mutation path.
Index ¶
- Variables
- func Delete(ctx context.Context, deps DeleteDeps, req prototypes.MemoryDeleteRequest, ...) (prototypes.MemoryDeleteResponse, error)
- func Get(ctx context.Context, deps GetDeps, req prototypes.MemoryGetRequest, ...) (prototypes.MemoryGetResponse, error)
- func Health(ctx context.Context, deps HealthDeps, id identity.Quadruple) (prototypes.MemoryHealthResponse, error)
- func List(ctx context.Context, deps ListDeps, req prototypes.MemoryListRequest, ...) (prototypes.MemoryListResponse, error)
- func Put(ctx context.Context, deps PutDeps, req prototypes.MemoryPutRequest, ...) (prototypes.MemoryPutResponse, error)
- func StrategyTrace(ctx context.Context, deps StrategyTraceDeps, id identity.Quadruple) (prototypes.MemoryStrategyTraceResponse, error)
- type DeleteDeps
- type GetDeps
- type HealthDeps
- type ListDeps
- type PutDeps
- type StrategyTraceDeps
Constants ¶
This section is empty.
Variables ¶
var ErrContextLeak = errors.New("memory/protocol: heavy memory value reached the response path as raw inline bytes (D-026)")
ErrContextLeak — Get materialised a memory record value whose byte length meets or exceeds the heavy-content threshold but the value reached the response path as raw inline bytes rather than an ArtifactStub. Mirrors `llm.ErrContextLeak` (D-026 / CLAUDE.md §13): a heavy value MUST route through the ArtifactStore by reference; an inline heavy value is a leak and is failed loudly, never truncated.
var ErrInvalidFilter = errors.New("memory/protocol: invalid memory.list filter")
ErrInvalidFilter — a memory.list filter carried a structurally invalid value: an unknown scope / driver / strategy enum, or a negative page / page-size. The caller maps this onto `CodeInvalidRequest`. Fails loudly — a malformed filter is never a silently-dropped facet (CLAUDE.md §13).
var ErrPageOutOfRange = errors.New("memory/protocol: page/page_size out of range")
ErrPageOutOfRange — a memory.list request asked for a page-size above the documented maximum (or a negative page / page-size). Distinct from ErrInvalidFilter so the caller can render a precise message; both map onto `CodeInvalidRequest`.
Functions ¶
func Delete ¶ added in v1.3.0
func Delete(ctx context.Context, deps DeleteDeps, req prototypes.MemoryDeleteRequest, id identity.Quadruple) (prototypes.MemoryDeleteResponse, error)
Delete evicts ONE conversation turn (by key) from the caller's session memory via a `Snapshot` → drop-the-keyed-turn → `Restore` read-modify- write. The Record envelope — including the rolling-summary text — is preserved losslessly (`memory.Record.Summary`). A key that matches no turn fails loudly with `memory.ErrNotFound` (never a silent no-op). Emits the `memory.item_deleted` audit event on success.
func Get ¶
func Get(ctx context.Context, deps GetDeps, req prototypes.MemoryGetRequest, id identity.Quadruple) (prototypes.MemoryGetResponse, error)
Get answers the `memory.get` Protocol method: it resolves a single memory record by key within the caller's identity scope and returns the full detail — metadata + post-redaction value (below the heavy-content threshold) OR a `MemoryArtifactRef` (at or above it).
Identity is mandatory (D-001). The heavy-value bypass (D-026) is enforced: a record value at or above HeavyThreshold is routed through the ArtifactStore and the detail ships `ValueArtifact`; the inline `Value` is left empty. EXACTLY ONE of Value / ValueArtifact is populated. A value that somehow reached the inline path while being heavy is a leak — Get fails loudly with `ErrContextLeak` rather than inlining it (mirrors the LLM-edge enforcement in `internal/llm/safety.go`).
A key that resolves to no record returns `memory.ErrNotFound` — the caller maps it onto `CodeNotFound`.
func Health ¶
func Health(ctx context.Context, deps HealthDeps, id identity.Quadruple) (prototypes.MemoryHealthResponse, error)
Health answers the `memory.health` Protocol method: it returns the aggregate memory-health counters (total records / expiring-in-1h / identity-rejected-24h / recovery-dropped-24h) plus the per-scope driver mapping.
Identity is mandatory (D-001): an incomplete triple on id fails loudly with `memory.ErrIdentityRequired`. The record counters derive from the caller's per-identity snapshot; the 24-hour event counters derive from the events Aggregator (when wired); the driver mapping derives from the configured per-scope driver split.
func List ¶
func List(ctx context.Context, deps ListDeps, req prototypes.MemoryListRequest, id identity.Quadruple) (prototypes.MemoryListResponse, error)
List answers the `memory.list` Protocol method: it projects the caller's per-identity memory snapshot into the Console-page row shape, applies the request's facet filters, paginates, and attaches the aggregate counters.
Identity is mandatory (D-001): an incomplete triple on id fails loudly with `memory.ErrIdentityRequired`. The cross-tenant scope gate is the caller's job (the stream handler checks `auth.HasScope` before calling List) — by the time List runs the request is authorised; List filters strictly within the supplied identities.
The filter's facets are validated up front: an unknown scope / driver / strategy enum, or a page / page-size out of range, fails loudly (ErrInvalidFilter / ErrPageOutOfRange) — never a silently dropped facet (CLAUDE.md §13).
func Put ¶ added in v1.3.0
func Put(ctx context.Context, deps PutDeps, req prototypes.MemoryPutRequest, id identity.Quadruple) (prototypes.MemoryPutResponse, error)
Put appends an operator-supplied conversation turn to the caller's session memory via the shipped `AddTurn`, then emits the `memory.item_put` audit event. Returns the deterministic key of the appended turn (the value a subsequent `memory.get` resolves). Identity is validated before the store is touched; admin-gating is the handler's job.
func StrategyTrace ¶ added in v1.3.0
func StrategyTrace(ctx context.Context, deps StrategyTraceDeps, id identity.Quadruple) (prototypes.MemoryStrategyTraceResponse, error)
StrategyTrace projects how the configured memory strategy is compacting the caller's session memory right now — the strategy's live `GetLLMContext` (rolling summary + verbatim turns + token estimate) plus `Health`. Identity is validated before the store is touched; a missing triple fails loudly with `memory.ErrIdentityRequired` (D-001 / D-033).
Types ¶
type DeleteDeps ¶ added in v1.3.0
type DeleteDeps struct {
// Store is the memory store the turn is evicted from. Mandatory.
Store memory.MemoryStore
// Bus is the events bus the audit event publishes on. Optional.
Bus events.EventBus
}
DeleteDeps carries the Delete dependencies.
type GetDeps ¶
type GetDeps struct {
// Store is the memory subsystem the snapshot is projected from.
Store memory.MemoryStore
// Artifacts is the ArtifactStore heavy values (≥ HeavyThreshold)
// are routed through (D-026). Mandatory — a nil fails loud.
Artifacts artifacts.ArtifactStore
// DriverName is the configured memory-driver name surfaced on the
// returned row.
DriverName string
// HeavyThreshold is the configured heavy-content byte size
// (cfg.Artifacts.HeavyOutputThresholdBytes). A value whose byte
// length meets or exceeds it routes through the ArtifactStore. A
// non-positive threshold fails loud (a zero threshold would route
// every value).
HeavyThreshold int
}
GetDeps carries the dependencies Get composes over.
type HealthDeps ¶
type HealthDeps struct {
// Store is the memory subsystem the snapshot + health are read
// from.
Store memory.MemoryStore
// Aggregator is the events Aggregator the 24-hour counters derive
// from. Optional — see ListDeps.Aggregator.
Aggregator *events.Aggregator
// DriverByScope is the configured per-scope driver mapping —
// e.g. {"session":"inmem", "tenant":"postgres"}. The caller
// supplies it from config; the MemoryStore interface does not
// expose a per-scope driver split. When unset, Health reports a
// single-scope mapping for the session scope under DriverName.
DriverByScope map[string]string
// DriverName is the configured memory-driver name, used to seed a
// single-scope DriverByScope mapping when DriverByScope is unset.
DriverName string
}
HealthDeps carries the dependencies Health composes over.
type ListDeps ¶
type ListDeps struct {
// Store is the memory subsystem the snapshot is projected from.
Store memory.MemoryStore
// Aggregator is the events Aggregator the 24-hour counters derive
// from. Optional — when nil, the IdentityRejected24h /
// RecoveryDropped24h counters are reported as 0 (the page still
// renders; the right-rail cards subscribe to the live event stream
// for the real-time view). The driver-comparison + record counters
// do not depend on it.
Aggregator *events.Aggregator
// DriverName is the configured memory-driver name surfaced on each
// row (`inmem` / `sqlite` / `postgres`). The MemoryStore interface
// does not expose it; the caller supplies it from config.
DriverName string
// HeavyThreshold is the configured heavy-content byte size
// (cfg.Artifacts.HeavyOutputThresholdBytes). It is the single
// classification point for the per-row HeavyContent flag (D-026);
// `memory.list` and `memory.get` MUST agree, so both read the same
// threshold. A zero / non-positive value disables the flag (no row
// is reported heavy) — the list still renders.
HeavyThreshold int
}
ListDeps carries the dependencies List composes over. All are validated at the call site (the stream handler) — a nil Store fails loud rather than nil-panicking mid-projection.
type PutDeps ¶ added in v1.3.0
type PutDeps struct {
// Store is the memory store the turn is appended to. Mandatory.
Store memory.MemoryStore
// Bus is the events bus the audit event publishes on. Optional — a
// nil bus skips the emit (test wiring); production always supplies it.
Bus events.EventBus
}
PutDeps carries the Put dependencies.
type StrategyTraceDeps ¶ added in v1.3.0
type StrategyTraceDeps struct {
// Store is the memory store the trace projects from. Mandatory.
Store memory.MemoryStore
}
StrategyTraceDeps carries the StrategyTrace dependencies.