Skip to content

feat(ltm): integrate Long-Term Memory package host - #4133

Merged
SpicyMarinara merged 3 commits into
Pasta-Devs:stagingfrom
Promansis:feature/long-term-memory-release
Jul 27, 2026
Merged

SpicyMarinara merged 3 commits into
Pasta-Devs:stagingfrom
Promansis:feature/long-term-memory-release

Conversation

@Promansis

@Promansis Promansis commented Jul 26, 2026 •

Copy link
Copy Markdown
Collaborator

Linked issue

Closes #4132

Why this change

  • The downloadable Long-Term Memory package needs stable Engine-owned contracts for data access, model routing, embeddings, privileged maintenance routes, prompt injection, settings UI, backups, and compatibility. Without these contracts, package code would have to duplicate or depend on private Engine internals.
  • This keeps package-owned behavior in Pasta-Devs/Marinara-Agents while keeping host integration in Marinara Engine, matching the repository ownership boundary.

What changed

  • Added capability hosts for stable local embeddings, richer language-model requests, chat/resource access, and package-owned privileged routes.
  • Added the narrow Long-Term Memory runtime bridge for recall and accepted-prompt accounting.
  • Integrated recalled context with normal prompt assembly, preset markers, cached regeneration, package settings, and backup coverage.
  • Preserved cumulative capability API 1.x compatibility and hardened package route namespaces, authorization order, activation rollback, and embedding input bounds.
  • Added focused lifecycle and prompt regression coverage.
  • Added Long-Term Memory to the downloadable-agent overview and reference; translation follow-up is tracked in [docs-i18n] docs/agents/agents-overview.md, docs/agents/built-in-agents.md #4131.

Validation

Automated commands completed locally:

  • pnpm check

  • pnpm --filter @marinara-engine/server exec tsx ../../scripts/regressions/capability-package-lifecycle.regression.ts

  • pnpm regression:prompt

  • pnpm --filter @marinara-engine/server exec tsx ../../scripts/regressions/open-issues.regression.ts

  • pnpm check passes locally

  • Container (Docker / Podman) built and ran without issue

  • Ran the app, clicked through the changes manually

  • Checked edge cases (light + dark mode, mobile viewport, empty states, error paths)

  • Above manual verification completed (describe below)

  • Read and followed CONTRIBUTING.md

Manual verification notes

  • Manually install the matching Long-Term Memory package artifact and restart Marinara.
  • Manually enable Long-Term Memory in Conversation, Roleplay, Visual Novel, and Game chats.
  • Manually verify package settings open, edit, persist, and respect the guarded Chat Settings close flow.
  • Manually verify recalled memories appear in the generated prompt at default placement and an explicit preset marker.
  • Manually verify regeneration reuses cached context without recording duplicate prompt accounting.
  • Manually verify vault maintenance/import/export routes require privileged access.
  • Manually verify backup and restore preserve package-owned Long-Term Memory data.
  • Manually verify desktop/mobile and light/dark presentation; attach screenshots or recordings before ready-for-review.
  • Manually build and run the container path.

Risk And Proof Boundary

  • Core claim: an installed Long-Term Memory package can use stable Engine hosts and contribute recalled context without package implementation code living in Engine.
  • Risk types: prompt assembly, provider request shaping, package storage/backups, privileged routes, compatibility, and dynamic package UI.
  • Entrypoints covered: generation route, capability runtime activation, package routes, package settings mount, preset sections, and backup roots.
  • Automated positive coverage: capability API compatibility, host availability, aggregate embedding bounds, package route namespace/duplicate guards, preset marker placement, fallback placement, and guarded settings navigation.
  • Automated negative controls: unsupported capability versions, invalid route namespaces, normalized duplicate routes, missing runtime service, failed recall/accounting, and unmatched preset sections.
  • Proof gaps: real package artifact installation, browser UI, container runtime, backup/restore round trip, and provider-backed extraction remain manual.

Docs and release impact

  • No docs changes needed
  • Updated docs (README / CONTRIBUTING / android/README / CHANGELOG) as needed
  • Translated docs on the docs-i18n branch updated to match, or a [docs-i18n] follow-up issue opened (see CONTRIBUTING.md § Translated documentation)
  • Version/release files updated (only if this PR includes a version bump)

Documentation was updated in docs/agents/agents-overview.md and docs/agents/built-in-agents.md. Translation follow-up: #4131. No version bump is included.

UI evidence (if applicable)

Not yet attached. Screenshots or recordings are required before moving this draft to ready for review.

Summary by CodeRabbit

  • New Features
    • Added Long-Term Memory as an installable capability package.
    • Long-Term Memory can be enabled from chat settings and included in preset sections.
    • Chat responses can recall relevant saved context and record accepted prompts.
    • Long-Term Memory data is included in backups and profile exports.
  • Documentation
    • Updated agent catalog counts and added Long-Term Memory setup, compatibility, settings, and data lifecycle details.
  • Bug Fixes
    • Improved settings drawer safeguards when closing before opening agent configuration.

@github-actions github-actions Bot added the feature New feature or enhancement label Jul 26, 2026
@coderabbitai

coderabbitai Bot commented Jul 26, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@SpicyMarinara, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f3f6d7c7-ad7a-44ee-aa9d-b95fb60af884

📥 Commits

Reviewing files that changed from the base of the PR and between 1c2faa7 and c73517e.

📒 Files selected for processing (7)
  • packages/client/src/components/chat/ChatSettingsDrawer.tsx
  • packages/server/src/routes/capability-packages.routes.ts
  • packages/server/src/services/capability-packages/capability-language-model.service.ts
  • packages/server/src/services/capability-packages/capability-route-registration.service.ts
  • packages/server/src/services/capability-packages/package-manager.service.ts
  • packages/server/src/services/generation/long-term-memory-runtime.ts
  • scripts/regressions/capability-package-lifecycle.regression.ts
📝 Walkthrough

Walkthrough

Long-Term Memory support is added across capability contracts, package runtime services, prompt generation, client settings, backups, documentation, and regression tests. Capability API compatibility advances from minor version 3 to 6.

Changes

Long-Term Memory host integration

Layer / File(s) Summary
Capability contracts and runtime hosts
packages/shared/..., packages/server/src/services/capability-packages/..., packages/server/src/services/local-embedder.ts, scripts/regressions/capability-package-lifecycle.regression.ts
Capability APIs now expose embeddings, package-scoped agent configuration, expanded resource and persistence access, richer language-model resolution, cancellation, and API minor version 6.
Privileged package routes
packages/server/src/services/capability-packages/capability-route-registration.service.ts, packages/server/src/services/capability-packages/capability-module-runtime.service.ts, scripts/regressions/capability-package-lifecycle.regression.ts
Capability packages can register permission-gated routes under scoped /api/<packageId> paths with collision checks, cleanup, and rollback.
Long-Term Memory generation flow
packages/server/src/routes/generate*, packages/server/src/services/generation/*, scripts/regressions/prompt.regression.ts
Generation performs bounded memory recall, places memory in runtime sections or separate injections, handles regeneration caches, and records accepted prompts once per iteration.
Client integration, assets, and documentation
packages/client/src/components/..., packages/server/src/routes/backup.routes.ts, docs/agents/*, scripts/regressions/open-issues.regression.ts
The installed capability appears in chat and preset settings, its data is included in backups, and its catalog entry and settings are documented.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: spicymarinara

Poem

Memory wakes beneath the prompt,
Routes bloom where packages mount.
Embeddings hum, receipts flow,
Old thoughts return where markers glow.
The drawer closes, backups keep—
Long-Term Memory guards the deep.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: integrating the Long-Term Memory package host.
Description check ✅ Passed The description follows the template well, with linked issue, rationale, changes, validation, docs impact, and manual notes filled in.
Linked Issues check ✅ Passed The changes align with #4132 by adding capability hosts, privileged routes, runtime recall/accounting, prompt injection, backups, compatibility, and docs.
Out of Scope Changes check ✅ Passed No clear out-of-scope changes are present; the added docs, tests, and service updates all support the Long-Term Memory integration.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@SpicyMarinara
SpicyMarinara marked this pull request as ready for review July 26, 2026 16:58
@SpicyMarinara

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 26, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@SpicyMarinara SpicyMarinara self-assigned this Jul 27, 2026
…memory-release

# Conflicts:
#	packages/server/src/routes/generate.routes.ts
@SpicyMarinara

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/server/src/routes/generate.routes.ts (1)

5748-5780: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Recording "acceptance" before the experiment has even concluded!

In the tool-call loop (Line 5466), recordAcceptedLongTermMemoryPrompt fires only after the full chatComplete call resolves. But in this non-tool streaming path, it fires at Line 5750 — right after the very first gen.next() — before the while (!result.done) loop has drained the rest of the stream. Should a later gen.next() throw (mid-stream provider failure, dropped connection, etc.), the catch block below simply returns null, yet the Long-Term Memory package has already been told the prompt was "accepted." A minor inconsistency that quietly corrupts the very accounting this feature exists to provide.

🔬 Proposed fix — record only after the specimen survives in full
             try {
               let result = await withLlmRequestTimeout(chatGenerationTimeoutMs, () => gen.next());
-              await recordAcceptedLongTermMemoryPrompt(initialProviderMessages);
               while (!result.done) {
                 if (abortController.signal.aborted) {
                   return null;
                 }
                 fullResponse += result.value;
                 // ...
                 result = await withLlmRequestTimeout(chatGenerationTimeoutMs, () => gen.next());
               }
+              await recordAcceptedLongTermMemoryPrompt(initialProviderMessages);
               // Generator return value contains usage
               if (result.value) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server/src/routes/generate.routes.ts` around lines 5748 - 5780, Move
the recordAcceptedLongTermMemoryPrompt call out of the initial gen.next() setup
and execute it only after the while (!result.done) loop completes successfully,
preserving the existing abort and error-return behavior so failed or interrupted
streams are never recorded as accepted.
🧹 Nitpick comments (3)
packages/server/src/routes/generate.routes.ts (1)

4596-4615: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

A redundant clone of my own creation.

Curious — you have already forged the elegant placeRuntimeAgentInjection helper (Lines 4182-4188) and put it to good use for knowledge-retrieval and knowledge-router (Lines 4456-4461, 4472-4473), yet the Long-Term Memory recall here reimplements the identical token-replace-or-append-or-drop dance by hand. Logically equivalent today, yes — but two specimens of the same organism drift apart over time, and the next mutation to placeRuntimeAgentInjection will silently fail to propagate here.

♻️ Proposed consolidation
         if (recall) {
-          const tokens = runtimeAgentSectionTokens.get("long-term-memory");
-          const handledByPresetSection =
-            tokens !== undefined && replaceRuntimeAgentSection(finalMessages, tokens, recall.text);
-          if (handledByPresetSection || !presetOwnsAgentPlacement) {
-            if (!handledByPresetSection) appendSeparateAgentInjection("long-term-memory", recall.text);
-            contextInjections.push({ agentType: "long-term-memory", text: recall.text });
+          const injection = { agentType: "long-term-memory", text: recall.text };
+          if (placeRuntimeAgentInjection(injection)) {
+            contextInjections.push(injection);
             longTermMemoryRecallReceipt = recall.receipt;
           }
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server/src/routes/generate.routes.ts` around lines 4596 - 4615,
Replace the manual runtime-section placement logic in the long-term-memory
recall block with the existing placeRuntimeAgentInjection helper, passing the
recall text and relevant agent type. Preserve the current behavior of replacing
preset-owned sections, appending when placement is not preset-owned, and
skipping injection when placement is unavailable; continue recording
contextInjections and longTermMemoryRecallReceipt only when placement succeeds.
scripts/regressions/capability-package-lifecycle.regression.ts (1)

198-241: 📐 Maintainability & Code Quality | 🔵 Trivial

Good coverage of the logical contract — but the mock can't see Fastify's boot-order guard.

routeApp is a bare { hasRoute, route } stub, so these assertions correctly validate prefix/duplicate-detection logic, but they can't catch the runtime hazard raised at capability-route-registration.service.ts (Lines 56-134): a real Fastify instance rejects route() calls once it has started listening. Worth an integration-level regression (or at least a code comment) once that concern is resolved, so the two don't silently diverge again.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/regressions/capability-package-lifecycle.regression.ts` around lines
198 - 241, Add integration-level regression coverage for
registerCapabilityPrivilegedRoutes using a real Fastify instance, exercising
route registration after the server has started listening and verifying the
expected boot-order behavior. Keep the existing routeApp unit-style assertions
for prefix and duplicate-route validation, and align the test with the
implementation in registerCapabilityPrivilegedRoutes.
packages/server/src/services/capability-packages/capability-resources.service.ts (1)

47-58: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

An N+1 experiment I did not authorize, Doctor.

listLorebooks issues one listEntries(record.id) call per lorebook. Your own listEligibleLorebookEntries a few lines below already demonstrates the correct instrument: lorebooks.listEntriesByLorebooks(ids) fetches entries for many lorebooks in a single batched call. Reuse it here instead of the per-record loop.

♻️ Proposed batched fetch
     async listLorebooks(lorebookIds): Promise<CapabilityLorebookRecord[]> {
       const requestedIds = lorebookIds ? uniqueStrings(lorebookIds) : null;
       const records = (requestedIds
         ? await Promise.all(requestedIds.map((lorebookId) => lorebooks.getById(lorebookId)))
         : await lorebooks.list()) as Array<({ id: string } & Record<string, unknown>) | null>;
-      return Promise.all(
-        records
-          .flatMap((record) =>
-            record ? [{ id: record.id, data: record, entries: lorebooks.listEntries(record.id) }] : [],
-          )
-          .map(async (record) => ({ ...record, entries: await record.entries })),
-      );
+      const validRecords = records.filter((record): record is NonNullable<typeof record> => Boolean(record));
+      const entriesByLorebook = await lorebooks.listEntriesByLorebooks(validRecords.map((record) => record.id));
+      const entriesById = new Map<string, unknown[]>();
+      for (const entry of entriesByLorebook as Array<{ lorebookId: string }>) {
+        entriesById.set(entry.lorebookId, [...(entriesById.get(entry.lorebookId) ?? []), entry]);
+      }
+      return validRecords.map((record) => ({
+        id: record.id,
+        data: record,
+        entries: entriesById.get(record.id) ?? [],
+      }));
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/server/src/services/capability-packages/capability-resources.service.ts`
around lines 47 - 58, Update listLorebooks to fetch all lorebook entries through
a single lorebooks.listEntriesByLorebooks call using the requested record IDs,
then associate the returned entries with each lorebook by ID. Remove the
per-record lorebooks.listEntries(record.id) calls while preserving the existing
output shape and handling records with no entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/client/src/components/chat/ChatSettingsDrawer.tsx`:
- Around line 8896-8930: The isGame rendering branch currently lacks the
Long-Term Memory package settings. Add a matching agent.id ===
"long-term-memory" && ltmPackage CapabilityElement case alongside the existing
hierarchical-maps handling, passing the same chatSettings and callbacks used in
the non-Game branch so Game chats expose the controls while preserving their
existing toggle behavior.

In
`@packages/server/src/services/capability-packages/capability-language-model.service.ts`:
- Around line 29-44: Update chatComplete to pass options.reasoningEffort
directly to provider.chatComplete instead of converting "none" to undefined.
Preserve the existing handling of all other completion options and ensure an
explicit "none" request reaches the provider unchanged.

In
`@packages/server/src/services/capability-packages/capability-route-registration.service.ts`:
- Around line 56-134: Update registerCapabilityPrivilegedRoutes so activation
after Fastify has started does not call app.route for previously unseen routes,
while preserving normal registration before startup and existing-slot
reactivation. Add an appropriate post-startup guard or runtime-safe registration
path around app.route, and ensure activatePackage remains able to install
packages with privileged routes without FST_ERR_INSTANCE_ALREADY_LISTENING.

In `@packages/server/src/services/generation/long-term-memory-runtime.ts`:
- Around line 30-56: Update recallLongTermMemory and
recordLongTermMemoryPromptAccepted to wrap their service.recall and
service.recordPromptAccepted awaits with the existing withLlmRequestTimeout(...)
helper, using the appropriate request timeout and preserving caller cancellation
where supported. Ensure both capability calls terminate within the bounded
timeout, including recordPromptAccepted which has no input.signal.

---

Outside diff comments:
In `@packages/server/src/routes/generate.routes.ts`:
- Around line 5748-5780: Move the recordAcceptedLongTermMemoryPrompt call out of
the initial gen.next() setup and execute it only after the while (!result.done)
loop completes successfully, preserving the existing abort and error-return
behavior so failed or interrupted streams are never recorded as accepted.

---

Nitpick comments:
In `@packages/server/src/routes/generate.routes.ts`:
- Around line 4596-4615: Replace the manual runtime-section placement logic in
the long-term-memory recall block with the existing placeRuntimeAgentInjection
helper, passing the recall text and relevant agent type. Preserve the current
behavior of replacing preset-owned sections, appending when placement is not
preset-owned, and skipping injection when placement is unavailable; continue
recording contextInjections and longTermMemoryRecallReceipt only when placement
succeeds.

In
`@packages/server/src/services/capability-packages/capability-resources.service.ts`:
- Around line 47-58: Update listLorebooks to fetch all lorebook entries through
a single lorebooks.listEntriesByLorebooks call using the requested record IDs,
then associate the returned entries with each lorebook by ID. Remove the
per-record lorebooks.listEntries(record.id) calls while preserving the existing
output shape and handling records with no entries.

In `@scripts/regressions/capability-package-lifecycle.regression.ts`:
- Around line 198-241: Add integration-level regression coverage for
registerCapabilityPrivilegedRoutes using a real Fastify instance, exercising
route registration after the server has started listening and verifying the
expected boot-order behavior. Keep the existing routeApp unit-style assertions
for prefix and duplicate-route validation, and align the test with the
implementation in registerCapabilityPrivilegedRoutes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b2e7cfe9-4729-40ca-bf09-5c056b28a475

📥 Commits

Reviewing files that changed from the base of the PR and between c715b1d and 1c2faa7.

📒 Files selected for processing (21)
  • docs/agents/agents-overview.md
  • docs/agents/built-in-agents.md
  • packages/client/src/components/chat/ChatSettingsDrawer.tsx
  • packages/client/src/components/presets/PresetEditor.tsx
  • packages/server/src/routes/backup.routes.ts
  • packages/server/src/routes/generate.routes.ts
  • packages/server/src/routes/generate/generate-route-utils.ts
  • packages/server/src/services/capability-packages/capability-embedding.service.ts
  • packages/server/src/services/capability-packages/capability-language-model.service.ts
  • packages/server/src/services/capability-packages/capability-module-runtime.service.ts
  • packages/server/src/services/capability-packages/capability-persistence.service.ts
  • packages/server/src/services/capability-packages/capability-resources.service.ts
  • packages/server/src/services/capability-packages/capability-route-registration.service.ts
  • packages/server/src/services/generation/long-term-memory-runtime.ts
  • packages/server/src/services/generation/runtime-agent-sections.ts
  • packages/server/src/services/local-embedder.ts
  • packages/shared/src/schemas/capability-package.schema.ts
  • packages/shared/src/types/capability-runtime.ts
  • scripts/regressions/capability-package-lifecycle.regression.ts
  • scripts/regressions/open-issues.regression.ts
  • scripts/regressions/prompt.regression.ts

Comment thread packages/client/src/components/chat/ChatSettingsDrawer.tsx
Comment thread packages/server/src/services/generation/long-term-memory-runtime.ts
@SpicyMarinara

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@SpicyMarinara SpicyMarinara left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainer review: merge conflict resolved, all CodeRabbit findings addressed and confirmed, review threads resolved, and local plus GitHub validation is green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants