Extend the plugin memory SPI with a three-arg prefetch(agentId, userQuery, ownerKey) default method and forward ownerKey through PluginMemoryBridge, enabling per-owner isolated recall for external providers. Ship mateclaw-plugin-mem0: an optional, zero-extra-dependency plugin that bridges a self-hosted Mem0 service (semantic recall via /memories/search/, async turn sync via /memories/) with full fault isolation — not part of the default stack. Includes 42 tests and bilingual user docs.
mate_memory_recall.filename is VARCHAR(256), but the snippet-level recall
tracker assembles the key as `path + '#' + H2-heading-slug`. When the LLM
writes an over-long daily-note heading (the summarize prompt placed no
length cap on the `##` title), the CJK-preserving slug pushes the filename
past the column, and writes fail with Data too long / string too long.
Three layers of defence, root cause + hard caps:
1. prompt (source) — summarize-system.txt now asks for short (≤30 chars)
`##` titles; details go in the body, not the heading.
2. slug cap (close to source) — MemoryRecallTracker.sanitizeSectionKey
caps the slug at MAX_SECTION_SLUG=200, leaving path+'#' well under 256.
3. write-side cap (catches every path) — MemoryRecallService.recordRecall
truncates filename to MAX_FILENAME_LENGTH=255 at the entry point, so
the select/insert/update branches share one value and the dup-key
concurrency fallback still matches. Covers trackActiveRetrieval too,
which bypasses sanitizeSectionKey.
Tests: MemoryRecallFilenameTruncationTest covers both caps (over-long CJK
heading, normal heading untouched, ascii slug, date prefix survives) plus
an end-to-end assertion that the stored value fits VARCHAR(256). Existing
memory-suite unit tests still green.
@SpringBootTest + H2 coverage asserting that both listRecent and search exclude
a still-running sibling conversation (stream_status='running') and the caller's
own current conversation, so concurrent sessions of the same agent cannot leak
into each other's session_search results.
Always-on memory (structured user/feedback blocks, PROFILE.md, MEMORY.md) is injected into every system prompt but only ever grew, inflating per-turn context over time. This adds deterministic size control across all always-on sources:
- Injection budget: cap the always-on structured block by total chars and per-type entry count, keeping the most-recently-updated entries (LRU by Updated date) and disclosing how many were omitted
- Nightly consolidation: a dedicated scheduled pass merges duplicate/stale user & feedback entries via the LLM, preserving each entry's original Updated date; runs per owner bucket (shared + personal) with a per-run cap and a never-grow safety guard
- File ceilings: deterministic backstop truncates PROFILE.md / MEMORY.md at a section boundary when a rewrite overruns its budget
- Manual trigger endpoint for the consolidation maintenance task
All knobs under mate.memory.*; covered by unit tests.
Both failed on dev independently of the recent merges (confirmed against a
pre-merge baseline):
- MemorySummarizationStructuredRoutingTest reflected applyStructuredEntries by
its old (Long, JsonNode) signature; owner-isolation added a trailing ownerKey
param. Update the reflective lookup to (Long, JsonNode, String) and the
remember() verifications to the 6-arg overload.
- ToolGuardCardHandlerTest still asserted the old 'system-owned pending accepts
any clicker' behavior, but the handler now rejects a group click on a
system/cron-owned approval fail-closed (no human requester to match), routing
it to the admin console. Assert no synthetic injection + the unauthorized card
render instead.
The PRIVATE_ITEMS list contained the bare 'test' entry, which rsync
interprets as 'any directory named test at any depth' — so it caught
the root-level /test/ scratch directory (intended) AND every src/test/
under each module (not intended).
Pattern is already anchored to /test (root-only). This commit rsyncs
the accumulated src/test/ tree forward so opensource has the unit tests
that have been written / updated against existing src/main/ code since
the pattern regression. Going forward each per-commit sync will carry
src/test/ files along with the main change.
Wire memory-facing events (turn-started, turn-completed, session-ended,
memory-written) through a single MemoryLifecycleMediator so
MemoryProvider implementations can hook into the agent conversational
flow without spreading side-effects across the runtime.
Ten atomic steps shipped under feat/dream-v2-p1-lifecycle:
- A.1 + A.2: MemoryLifecycleMediator class + TurnContext value object
- A.3: TurnStartedEvent / TurnCompletedEvent domain events
- A.4: MemoryLifecycleEventListener bean for Spring event plumbing
- A.5: MemoryProvider.onMemoryWrite default method (backward compatible)
- A.7: wire the mediator into AgentService at the right hook points
- A.8: LifecycleFlagGuardTest — feature flag must gate every hook
- A.9: MemoryLifecycleMediatorTest — unit coverage per hook
- A.10: LifecycleRecallCountIT — F4 regression across the stack
Feature flags (all default OFF; enable per phase after staging):
- mate.memory.lifecycle-mediator-enabled
- mate.memory.dream.focused-enabled
- mate.memory.dream.archive-enabled
This is Phase 1 foundation only — focused-dream and archive-dream
providers arrive in later phases.