Added
- (memory) add Compactor trait, CompactingMemory adapter, and TemplateCompactor (#1748) (by @ForeverAngry)
- (memory) Rig-managed conversation memory + rig-memory companion crate (#1702) (by @ForeverAngry)
Other
- Memory adapter cancellation safety and trait-object forwarding (#1756) (by @gold-silver-copper) - #1756
- Add demotion hooks for bounded conversation memory (#1737) (by @ForeverAngry) - #1737
Contributors
Added
-
Compactortrait (re-exported fromrig_core::memory) +
CompactingMemory<M, P, C>adapter and aTemplateCompactor
reference implementation. WhereDemotingPolicyMemoryonly
observes messages a policy truncates out of active history, a
Compactorsubstitutes them: it derives a singleMessage-shaped
Artifactfrom the evicted prefix (and, optionally, the previous
summary), andCompactingMemorysplices that artifact at the front
of the loaded history. The resulting prompt shape is
[summary_message, ...kept_window], with the summary rolling
forward on every load that produces newly-evicted messages — the
canonical recursive-summary pattern for long-running agents.
Concurrent loads on the sameconversation_idare serialised at
the compaction seam via an in-flight gate: only one load at a time
invokes the compactor; others observe the gate and immediately
return the previously-stored summary spliced in front ofkept,
without re-running the compactor. Watermarks and the carry-over
summary are in-process only —Compactorimplementations with
durable side effects (LLM calls, vector-store writes) must
deduplicate.cleardrops the carry-over so a freshly-populated
backend re-compacts from scratch.TemplateCompactoris a
zero-dependency, no-LLM rollup useful as a default and for tests;
it produces aTextSummarythat converts into aMessage::System
with header + previous-summary + per-linerole: textbody. The
rollup represents out-of-band context about the prior conversation
rather than a turn from any participant, so the system role is the
semantically correct framing across providers.TemplateCompactor
exposeswith_max_bytesso long-running conversations can cap the
rolled-up text; when exceeded, the oldest portion of the body is
dropped at a UTF-8 boundary and replaced with a"[…truncated…]"
marker, preserving the most recent context. Note that the spliced
summary sits outside the wrappedMemoryPolicy's budget, so
pairingCompactingMemorywith a token-budgeted policy requires a
bounded compactor (or accepting that the loaded prompt may exceed
the policy budget by the artifact size). -
DemotionHooktrait +DemotingPolicyMemory<M, P, H>adapter and a
NoopDemotionHookno-op default. The trait itself lives in
rig_core::memory(re-exported here) so any memory backend can
implement it without taking arig-memorydependency; the composing
adapter lives in this crate.DemotingPolicyMemorycalls the hook
with messages that the policy truncated out of active history,
turning eviction into demotion. It tracks per-conversation demotion
watermarks so repeatedloadcalls do not replay the same demoted
messages into append-only long-tail stores. Concurrent loads on the
sameconversation_idare serialised at the demotion seam via an
in-flight gate: only one load at a time delivers to the hook;
others observe the gate and return the truncated history without
re-firing. Watermarks are in-process only —DemotionHook
implementations must be idempotent on(conversation_id, messages)
to survive process restarts. BridgesSlidingWindowMemory/
TokenWindowMemoryto long-tail stores such asMemvidPersistHook
without coupling either crate to the other. -
DemotingPolicyMemory::forget(conversation_id)and
tracked_conversations()for explicit watermark-map cleanup and
leak diagnostics. Both are infallible: a poisoned internal lock
is treated as "nothing to forget / zero tracked" rather than a
caller-visible error. -
MemoryPolicy::apply_with_demotedcompanion method that reports
(kept, demoted).applyremains the required method; the default
apply_with_demotedreturns(apply(...)?, Vec::new())so existing
policies keep compiling unchanged.SlidingWindowMemoryand
TokenWindowMemoryoverride it to populate the demoted prefix that
DemotingPolicyMemoryhands to the hook. -
HeuristicTokenCounter— provider-agnostic, zero-dependency
TokenCounterimplementation that approximates token cost from
UTF-8 byte lengths (str::len, O(1)). Shipsdefault/openai/
anthropic/geminipresets so
TokenWindowMemory::new(budget, HeuristicTokenCounter::default())
works out of the box without a tokenizer dependency. Also handles the
Message::Systemvariant and tool-call argument payloads. The
configurable ratio is namedbytes_per_tokento match the
implementation; for ASCII text bytes and characters coincide, and
for non-ASCII text the counter slightly over-estimates, which is the
safe direction for a hard budget. -
PolicyMemory<M, P>adapter — wrap anyConversationMemorywith a
MemoryPolicyand propagate policy failures to the caller as
MemoryError::Policy. Hard-fail counterpart to
InMemoryConversationMemory::with_filter+IntoFilter::into_filter,
which degrade to identity on policy error.