TencentCloud/TencentDB-Agent-Memory · warning

[context-offload] build L3 report failed: ${reportErr}

Error message

[context-offload] build L3 report failed: ${reportErr}

What it means

Inside the after_tool_call hook, after compaction completes, the plugin builds an L3 trigger report (token accounting) and hands it to reportL3Trigger for upload to the backend /store. The report construction/upload kick-off is wrapped in try/catch so a reporting failure never disrupts the agent loop; it is logged as this warning. The message interpolates the underlying error, e.g. a TypeError from a missing stateManager field or an exception thrown by buildL3TriggerReport.

Source

Thrown at MemoryCore/src/offload/hooks/after-tool-call.ts:339

          event,
          contextWindow: _contextWindow,
          mildThreshold: _mildThreshold,
          aggressiveThreshold: _aggressiveThreshold,
          tokensBefore: _tokensBefore,
          tokensAfter: _tokensAfter,
          messagesBefore: _msgsBefore,
          messagesAfter: _msgsAfter,
          durationMs: _compDuration,
          aboveMild: _tokensBefore >= _mildThreshold,
          aboveAggressive: _tokensBefore >= _aggressiveThreshold,
          mildReplacedCount: _compResult.mildReplacedCount ?? 0,
          aggressiveDeletedCount: _compResult.aggressiveDeletedCount ?? 0,
          emergencyTriggered: _compResult.emergencyTriggered ?? false,
          emergencyDeletedCount: _compResult.emergencyDeletedCount ?? 0,
        });
        reportL3Trigger(backendClient ?? null, report, logger);
      } catch (reportErr) {
        logger.warn(`[context-offload] build L3 report failed: ${reportErr}`);
      }
    }

    // Trace full messages snapshot at end of after_tool_call
    if (event.messages && Array.isArray(event.messages)) {
      traceMessagesSnapshot({
        sessionKey: stateManager.getLastSessionKey(),
        stage: "after_tool_call.end",
        messages: event.messages,
        label: `tool=${event.toolName}`,
        extra: {
          toolName: event.toolName,
          toolCallId,
          pendingCount: stateManager.getPendingCount(),
          activeMmdFile: stateManager.getActiveMmdFile() ?? null,
          l15Settled: stateManager.l15Settled,
        },
        logger,

View on GitHub (pinned to 3efcd317b8)

Solutions

  1. Read the interpolated reportErr in the log — it names the exact property or call that failed.
  2. Verify the compaction result shape matches what buildL3TriggerReport expects (upgrade/downgrade plugin and server to matching versions).
  3. Confirm stateManager was initialized for this session (no undefined snapshot fields).
  4. If a custom backendClient is passed, ensure reportL3Trigger-compatible methods do not throw synchronously.
  5. This only affects telemetry/token accounting, not compaction itself — the conversation continues normally; fix at leisure.

Example fix

// before: report build crashes on missing compaction detail
mildReplacedCount: _compResult.mildReplacedCount ?? 0,
aggressiveDeletedCount: _compResult.aggressiveDeletedCount ?? 0,
// after: harden the whole compaction-derived report input against null
const comp = _compResult ?? {};
// ...
mildReplacedCount: comp.mildReplacedCount ?? 0,
aggressiveDeletedCount: comp.aggressiveDeletedCount ?? 0,
emergencyTriggered: comp.emergencyTriggered ?? false,
Defensive patterns

Strategy: type-guard

Validate before calling

const comp = _compResult ?? {};
const safeReportInput = {
  mildReplacedCount: typeof comp.mildReplacedCount === "number" ? comp.mildReplacedCount : 0,
  aggressiveDeletedCount: typeof comp.aggressiveDeletedCount === "number" ? comp.aggressiveDeletedCount : 0,
  emergencyTriggered: comp.emergencyTriggered === true,
  emergencyDeletedCount: typeof comp.emergencyDeletedCount === "number" ? comp.emergencyDeletedCount : 0,
};

Type guard

function isReportableCompaction(c: unknown): c is CompactionResult {
  return c !== null && typeof c === "object" &&
    ("mildReplacedCount" in c || "aggressiveDeletedCount" in c);
}

Try / catch

try {
  const report = buildL3TriggerReport({ /* normalized inputs */ });
  reportL3Trigger(backendClient ?? null, report, logger);
} catch (reportErr) {
  logger.warn(`[context-offload] build L3 report failed: ${reportErr}`);
  // telemetry only — do not rethrow
}

Prevention

When it happens

Trigger: buildL3TriggerReport(...) throws while assembling the report — typically when _compResult or stateManager fields are undefined/null in a shape the code does not expect (e.g. a compaction result missing mildReplacedCount internals accessed beyond the ?? defaults), or when reportL3Trigger throws synchronously on a malformed backendClient; the backendClient ?? null guard covers null but not a client whose methods throw.

Common situations: Partial compaction results after a server error returned a degraded payload; plugin version mismatch where CompactionResult gained/lost fields; custom backendClient implementations that throw synchronously; corrupt in-memory stateManager state after a hot plugin reload.


AI-assisted analysis of TencentCloud/TencentDB-Agent-Memory@3efcd317b8 (2026-09-01). Data as JSON: /api/errors/45580dc62851f3c7. Report an issue: GitHub.