Skip to content

fix(compression): fallback to synthetic compression when memory provider is noop#757

Open
Chewji9875 wants to merge 1 commit into
rohitg00:mainfrom
Chewji9875:fix/compress-noop-fallback
Open

fix(compression): fallback to synthetic compression when memory provider is noop#757
Chewji9875 wants to merge 1 commit into
rohitg00:mainfrom
Chewji9875:fix/compress-noop-fallback

Conversation

@Chewji9875
Copy link
Copy Markdown

@Chewji9875 Chewji9875 commented Jun 1, 2026

WHAT

Implements an early return/fallback to local zero-LLM synthetic compression when the active memory provider is NoopProvider (noop).

WHERE

  • src/functions/compress.ts (Adds the provider.name === "noop" check to generate and write synthetic observations).
  • test/auto-compress.test.ts (Adds unit tests verifying the graceful fallback to synthetic compression and zero metrics pollution, while correcting environment variable cleanups in test lifecycles).

WHEN

When mem::compress is called (either manually or automatically via the observe hook) and the system has no active/valid API keys (defaulting to the noop provider).

WHY

Previously, the noop provider blindly passed empty strings to the LLM compressor, causing XML validation failures 100% of the time, polluting OTel metrics with failures, and leading to complete data loss since observations were never saved. This fallback ensures data integrity and clean metric logs.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added fallback to synthetic compression when using the noop provider, ensuring the compression pipeline continues to function gracefully in degraded configurations.
  • Tests

    • Enhanced compression tests to validate synthetic compression fallback behavior and proper data persistence to storage.

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 1, 2026

@Chewji9875 is attempting to deploy a commit to the rohitg00's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 63b760f9-b800-4635-91d7-55a11f7651ed

📥 Commits

Reviewing files that changed from the base of the PR and between ee68089 and 97c69c4.

📒 Files selected for processing (2)
  • src/functions/compress.ts
  • test/auto-compress.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/functions/compress.ts
  • test/auto-compress.test.ts

📝 Walkthrough

Walkthrough

This PR adds a synthetic compression fallback to the compression handler: when the memory provider is "noop", mem::compress builds a synthetic compressed observation, sets id/sessionId/timestamp, persists and indexes it, publishes it to streams, and returns success. Tests validate the fallback and KV persistence.

Changes

Synthetic Compression Fallback for Noop Provider

Layer / File(s) Summary
Noop provider synthetic compression implementation
src/functions/compress.ts
Imports buildSyntheticCompression and adds an early-return branch in mem::compress that detects the "noop" provider, constructs a synthetic compressed observation with id/sessionId/timestamp, persists to KV, indexes into BM25/vector stores with guarded error handling, publishes to streaming functions with non-fatal failure logging, and returns success with confidence-derived quality score.
Noop provider synthetic compression validation tests
test/auto-compress.test.ts
Updates test environment setup/teardown to explicitly set AGENTMEMORY_AUTO_COMPRESS = "false", and adds tests that register a noop compress provider and verify mem::compress returns a synthetic observation, does not record metrics, and persists the synthesized compressed observation to KV.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I built a faux-compress, quick and neat,
When noop arrives, I skip the heavy feat.
A synthetic note, with id and time,
Saved, indexed, streamed — all in rhyme.
thump-thump hop

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a fallback to synthetic compression when the memory provider is noop, which directly matches the primary objective of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

@Chewji9875 Chewji9875 changed the title fix(compression): graceful fallback to synthetic compression when memory provider is noop fix(compression): fallback to synthetic compression when memory provider is noop Jun 1, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
test/auto-compress.test.ts (1)

82-89: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

The “unset default” test is no longer testing an unset env.

Line 82 forces AGENTMEMORY_AUTO_COMPRESS="false" before the test at Line 88, so this case now overlaps with the explicit-false scenario and no longer validates true default behavior.

Suggested fix
 it("default (AGENTMEMORY_AUTO_COMPRESS unset): does NOT fire mem::compress", async () => {
+  delete process.env["AGENTMEMORY_AUTO_COMPRESS"];
   const { registerObserveFunction } = await import(
     "../src/functions/observe.js"
   );
🤖 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 `@test/auto-compress.test.ts` around lines 82 - 89, The test labeled "default
(AGENTMEMORY_AUTO_COMPRESS unset): does NOT fire mem::compress" is not actually
running with the env unset because beforeEach/afterEach force
AGENTMEMORY_AUTO_COMPRESS="false"; update the test setup so the env is truly
unset when importing registerObserveFunction: either remove the global
beforeEach/afterEach assignment of process.env["AGENTMEMORY_AUTO_COMPRESS"] =
"false" or explicitly delete process.env["AGENTMEMORY_AUTO_COMPRESS"] (e.g.,
delete process.env["AGENTMEMORY_AUTO_COMPRESS"]) at the start of that specific
test before the dynamic import of registerObserveFunction so the default
behavior is exercised.
🧹 Nitpick comments (2)
test/auto-compress.test.ts (1)

255-259: ⚡ Quick win

Assert that noop fallback does not call provider methods.

To lock the fallback contract, add explicit expectations that compress/summarize were not invoked.

Suggested assertions
     expect(mockMetricsStore.record).not.toHaveBeenCalled();
+    expect(mockProvider.compress).not.toHaveBeenCalled();
+    expect(mockProvider.summarize).not.toHaveBeenCalled();

Also applies to: 297-297

🤖 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 `@test/auto-compress.test.ts` around lines 255 - 259, Add explicit assertions
that the noop fallback does not invoke provider methods by asserting
mockProvider.compress and mockProvider.summarize were not called (e.g.,
expect(mockProvider.compress).not.toHaveBeenCalled() and
expect(mockProvider.summarize).not.toHaveBeenCalled()) in the test that
constructs mockProvider (the noop provider case) and duplicate the same
assertions in the other test covering the noop fallback (the one around the
second occurrence), ensuring you reference the mockProvider object used in those
tests.
src/functions/compress.ts (1)

110-142: ⚡ Quick win

Parallelize independent async side effects in noop path.

Line 110 awaits vector indexing before starting stream publishes at Line 117. These operations are independent, so running them together (e.g., a single Promise.allSettled) will reduce latency and isolate failures better.

As per coding guidelines: Use parallel operations where possible (Promise.all for independent kv writes/reads).

🤖 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 `@src/functions/compress.ts` around lines 110 - 142, The current code awaits
vectorIndexAddGuarded(...) before firing the two sdk.trigger(...) calls, but
those are independent; change to run all three side-effects concurrently by
removing the separate await and using a single Promise.allSettled([...]) that
includes the Promise returned by vectorIndexAddGuarded(synthetic.id,
synthetic.sessionId, ...) and the two sdk.trigger(...) calls (which reference
STREAM and TriggerAction.Void()); this will parallelize the operations and keep
failures isolated by handling their settled results.
🤖 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 `@src/functions/compress.ts`:
- Around line 93-163: The noop-fallback branch persists data and publishes
streams but never records an audit entry; add a call to recordAudit(...) before
returning success to satisfy the "state-changing operations" guideline.
Specifically, after the streamResults loop (and before logger.info and the
return) invoke recordAudit with the session id (data.sessionId), the observation
id (data.observationId or synthetic.id), an action string like
"compress:no-op-fallback" and relevant metadata (e.g., type: synthetic.type,
importance: synthetic.importance, compressed: true, confidence:
synthetic.confidence); await the call and handle/log any non-fatal errors
similarly to other async ops so failures don’t block the main flow.

---

Outside diff comments:
In `@test/auto-compress.test.ts`:
- Around line 82-89: The test labeled "default (AGENTMEMORY_AUTO_COMPRESS
unset): does NOT fire mem::compress" is not actually running with the env unset
because beforeEach/afterEach force AGENTMEMORY_AUTO_COMPRESS="false"; update the
test setup so the env is truly unset when importing registerObserveFunction:
either remove the global beforeEach/afterEach assignment of
process.env["AGENTMEMORY_AUTO_COMPRESS"] = "false" or explicitly delete
process.env["AGENTMEMORY_AUTO_COMPRESS"] (e.g., delete
process.env["AGENTMEMORY_AUTO_COMPRESS"]) at the start of that specific test
before the dynamic import of registerObserveFunction so the default behavior is
exercised.

---

Nitpick comments:
In `@src/functions/compress.ts`:
- Around line 110-142: The current code awaits vectorIndexAddGuarded(...) before
firing the two sdk.trigger(...) calls, but those are independent; change to run
all three side-effects concurrently by removing the separate await and using a
single Promise.allSettled([...]) that includes the Promise returned by
vectorIndexAddGuarded(synthetic.id, synthetic.sessionId, ...) and the two
sdk.trigger(...) calls (which reference STREAM and TriggerAction.Void()); this
will parallelize the operations and keep failures isolated by handling their
settled results.

In `@test/auto-compress.test.ts`:
- Around line 255-259: Add explicit assertions that the noop fallback does not
invoke provider methods by asserting mockProvider.compress and
mockProvider.summarize were not called (e.g.,
expect(mockProvider.compress).not.toHaveBeenCalled() and
expect(mockProvider.summarize).not.toHaveBeenCalled()) in the test that
constructs mockProvider (the noop provider case) and duplicate the same
assertions in the other test covering the noop fallback (the one around the
second occurrence), ensuring you reference the mockProvider object used in those
tests.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: dca6d6a0-bd48-4416-846f-6fed180d6c14

📥 Commits

Reviewing files that changed from the base of the PR and between fd9e3bd and ee68089.

📒 Files selected for processing (2)
  • src/functions/compress.ts
  • test/auto-compress.test.ts

Comment thread src/functions/compress.ts
Comment on lines +93 to +163
await kv.set(
KV.observations(data.sessionId),
data.observationId,
synthetic,
);

try {
getSearchIndex().add(synthetic);
} catch (err) {
logger.warn("Failed to index compressed observation into BM25", {
obsId: synthetic.id,
sessionId: synthetic.sessionId,
title: synthetic.title,
error: err instanceof Error ? err.message : String(err),
});
}

await vectorIndexAddGuarded(
synthetic.id,
synthetic.sessionId,
synthetic.title + " " + (synthetic.narrative || ""),
{ kind: "observation", logId: synthetic.id },
);

const streamResults = await Promise.allSettled([
sdk.trigger({
function_id: "stream::set",
payload: {
stream_name: STREAM.name,
group_id: STREAM.group(data.sessionId),
item_id: data.observationId,
data: { type: "compressed", observation: synthetic },
},
}),
sdk.trigger({
function_id: "stream::send",
payload: {
stream_name: STREAM.name,
group_id: STREAM.viewerGroup,
id: `compressed-${data.observationId}`,
type: "compressed_observation",
data: {
type: "compressed",
observation: synthetic,
sessionId: data.sessionId,
},
},
action: TriggerAction.Void(),
}),
]);

for (const res of streamResults) {
if (res.status === "rejected") {
logger.warn("Non-fatal stream publish failure after compress", {
sessionId: data.sessionId,
observationId: data.observationId,
error:
res.reason instanceof Error
? res.reason.message
: String(res.reason),
});
}
}

logger.info("Observation compressed (synthetic noop fallback)", {
obsId: data.observationId,
type: synthetic.type,
importance: synthetic.importance,
});

return { success: true, compressed: synthetic, qualityScore: synthetic.confidence * 100 };
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add audit logging for noop fallback mutations.

Line 93 persists data and Lines 117-142 publish stream events, but this branch never calls recordAudit(). Please record an audit entry for this state-changing path before returning success.

As per coding guidelines: Use recordAudit() for state-changing operations.

🤖 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 `@src/functions/compress.ts` around lines 93 - 163, The noop-fallback branch
persists data and publishes streams but never records an audit entry; add a call
to recordAudit(...) before returning success to satisfy the "state-changing
operations" guideline. Specifically, after the streamResults loop (and before
logger.info and the return) invoke recordAudit with the session id
(data.sessionId), the observation id (data.observationId or synthetic.id), an
action string like "compress:no-op-fallback" and relevant metadata (e.g., type:
synthetic.type, importance: synthetic.importance, compressed: true, confidence:
synthetic.confidence); await the call and handle/log any non-fatal errors
similarly to other async ops so failures don’t block the main flow.

@Chewji9875 Chewji9875 force-pushed the fix/compress-noop-fallback branch from ee68089 to 97c69c4 Compare June 4, 2026 16:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant