feat(cortex): log when relevant-memories injection is truncated by maxInjectionChars#6
feat(cortex): log when relevant-memories injection is truncated by maxInjectionChars#6100yenadmin wants to merge 5 commits into
Conversation
…xInjectionChars\n\nRefs #5
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (1)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Copilot wasn't able to review any files in this pull request.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
R-368 adversarial review findingsSpawned a separate adversarial reviewer (gpt-5.3-codex) on this patch right after filing. It found 4 real issues. Verdict: merge with tweaks, not as-is. Issues
RecommendationEither:
Full adversarial review report: |
| if (items.length > injectedCount) { | ||
| console.info(`[cortex] memories-injected=${injectedCount}/${items.length} chars=${charCount}/${maxChars}`); | ||
| lines.push(`[${injectedCount} of ${items.length} memories shown — use cortex_search for more]`); |
There was a problem hiding this comment.
🚩 Caller log message at line 1309 reports filtered.length not actual injected count
At dist/index.js:1309, the existing log says injecting ${filtered.length} memories but the actual number of memories injected may be lower due to the maxChars budget truncation and minScore filtering inside formatMemoryContext. With this PR adding explicit injectedCount tracking inside the function, there's now a discrepancy: the caller logs a potentially higher count than what was actually injected. This is a pre-existing issue but worth noting as the PR makes it more visible.
Was this helpful? React with 👍 or 👎 to provide feedback.
…gth > injectedCount
|
Sanitized local-vs-upstream diff audit for
Local-only flags:
Top 5 significant diffs (sanitized):
--- /tmp/r-370-upstream/dist/index.js 2026-04-10 01:38:03
+++ /Users/lume/.openclaw/extensions/cortex/dist/index.js 2026-04-02 21:47:25
@@ -1,1425 +1,3733 @@
-"use strict";
-/**
- * cortex — OpenClaw plugin bridging to Cortex HTTP API.
- *
- * Design principles:
- * - HTTP-only: pure fetch() to Cortex, no Python subprocesses
- * - Lazy injection: only inject memories when query seems memory-relevant
- * - Non-blocking capture: agent_end fires and forgets, never blocks gateway
- * - Token budget: hard cap on injected content (default 2000 tokens ~8000 chars)
- * - Graceful degradation: Cortex down → log warning, continue
- * - Session wake/sleep: non-blocking lifecycle calls
- * - Lane guards: skip injection/capture for heartbeat, boot, subagent, cron lanes
- * - Junk filter: drop trivial/noisy messages before capture
- *
- * Hooks:
- * before_agent_start → POST /api/v1/memories/retrieve → prependContext
- * agent_end → POST /api/v1/memories/remember → fire-and-forget
- * session_start → POST /api/v1/sessions/wake
- * session_end → POST /api/v1/sessions/sleep
- *
- * Tools: cortex_search, cortex_remember, cortex_forget, cortex_ask,
... [truncated]
--- /tmp/r-370-upstream/package-lock.json 2026-04-10 01:38:03
+++ /Users/lume/.openclaw/extensions/cortex/package-lock.json 2026-03-31 18:27:35
@@ -1,11 +1,11 @@
{
- "name": "@evaos/cortex",
+ "name": "cortex",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "@evaos/cortex",
+ "name": "cortex",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
@@ -13,14 +13,14 @@
},
"devDependencies": {
"@types/node": "^20.0.0",
- "openclaw": "*",
+ "openclaw": "^2026.3.28",
"typescript": "^5.0.0"
}
... [truncated]
--- /tmp/r-370-upstream/src/index.ts 2026-04-10 01:38:03
+++ /Users/lume/.openclaw/extensions/cortex/index.ts 2026-04-10 00:49:18
@@ -28,6 +28,9 @@
import { mkdirSync, existsSync } from "node:fs";
import { dirname, join } from "node:path";
+// Module-level flag: emit hardcoded-key warning only once per process lifetime
+let _apiKeyWarningEmitted = false;
+
// Dynamic require for node:sqlite (available in Node 22+, avoids TS import issues)
let NodeDatabaseSync: any;
try {
@@ -85,6 +88,10 @@
function resolveEnv(value: string): string {
return value.replace(/\$\{([^}]+)\}/g, (_, key) => process.env[key] ?? "");
+}
+
+function isEnvInterpolation(value: unknown): value is string {
+ return typeof value === "string" && /^\$\{[^}]+\}$/.test(value.trim());
}
function parseConfig(raw: unknown): EvaMemoryConfig {
@@ -98,7 +105,7 @@
... [truncated]
--- /tmp/r-370-upstream/tsconfig.json 2026-04-10 01:38:03
+++ /Users/lume/.openclaw/extensions/cortex/tsconfig.json 2026-03-31 18:28:27
@@ -1,21 +1,16 @@
{
"compilerOptions": {
"target": "ES2022",
- "module": "NodeNext",
- "moduleResolution": "NodeNext",
- "lib": ["ES2022"],
+ "module": "Node16",
+ "moduleResolution": "node16",
"outDir": "dist",
- "rootDir": "src",
- "declaration": true,
- "declarationMap": true,
- "sourceMap": true,
- "strict": true,
+ "rootDir": ".",
"esModuleInterop": true,
"skipLibCheck": true,
- "forceConsistentCasingInFileNames": true,
- "resolveJsonModule": true,
- "noEmitOnError": false
+ "strict": false,
... [truncated]
--- /tmp/r-370-upstream/package.json 2026-04-10 01:38:03
+++ /Users/lume/.openclaw/extensions/cortex/package.json 2026-03-31 18:27:35
@@ -1,24 +1,20 @@
{
- "name": "@evaos/cortex",
+ "name": "cortex",
"version": "1.0.0",
- "description": "Cortex memory engine plugin for OpenClaw — retrieval, storage, and lifecycle management",
+ "description": "Cortex memory engine plugin for OpenClaw / evaOS",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
- "prepublishOnly": "npm run build"
+ "dev": "tsc --watch"
},
- "files": [
- "dist/",
- "openclaw.plugin.json"
- ],
"dependencies": {
"@sinclair/typebox": "^0.32.0"
},
"devDependencies": {
... [truncated] |
| if (lines.length === 1) { | ||
| if (capHit) { | ||
| const serializedLength = lines.join("\n").length; | ||
| api.logger.info(`[cortex] memories-injected=0/${relevant.length} chars=${serializedLength}/${maxChars}`); |
There was a problem hiding this comment.
🔴 api is not in scope inside module-level formatMemoryContext, causing ReferenceError at runtime
formatMemoryContext is defined as a standalone module-level function at dist/index.js:756, but the newly added logging calls on lines 793 and 800 reference api.logger.info(...). The api variable is only in scope inside the register(api) closure starting at dist/index.js:873. When capHit is true (i.e., the character budget truncates memories), this will throw a ReferenceError: api is not defined, crashing the recall path and preventing any memory injection for that turn.
Prompt for agents
The formatMemoryContext function is a module-level function (dist/index.js:756) that does not have access to the api parameter — api is only available inside the register(api) closure (dist/index.js:873). The newly added api.logger.info() calls on lines 793 and 800 will throw ReferenceError at runtime whenever capHit is true.
Two possible approaches:
1. Accept a logger parameter in formatMemoryContext (e.g. add an optional logger argument) and pass api.logger from the call site at dist/index.js:1312.
2. Remove the logging from formatMemoryContext and instead do the logging at the call site inside register(), where api is in scope. The call site at line 1312 already has access to api.logger.
Also, these changes were made directly in dist/index.js without updating src/index.ts. The source file should be updated and the dist rebuilt from it.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const line = prefix | ||
| ? `- ${prefix} ${item.content}${tag}` | ||
| : `- ${item.content}${tag}`; | ||
| if (charCount + line.length > maxChars) | ||
| if (charCount + line.length > maxChars) { | ||
| capHit = true; | ||
| break; | ||
| } | ||
| lines.push(line); | ||
| charCount += line.length; | ||
| injectedCount++; | ||
| } | ||
| if (lines.length === 1) | ||
| if (lines.length === 1) { | ||
| if (capHit) { | ||
| const serializedLength = lines.join("\n").length; | ||
| api.logger.info(`[cortex] memories-injected=0/${relevant.length} chars=${serializedLength}/${maxChars}`); | ||
| } | ||
| return ""; // Only header, no items fit | ||
| } | ||
| if (capHit) { | ||
| lines.push(`[${injectedCount} of ${relevant.length} memories shown — use cortex_search for more]`); | ||
| const serializedLength = lines.join("\n").length; | ||
| api.logger.info(`[cortex] memories-injected=${injectedCount}/${relevant.length} chars=${serializedLength}/${maxChars}`); | ||
| } |
There was a problem hiding this comment.
🔴 Source file src/index.ts not updated — dist changes will be overwritten on next build
The PR only modifies dist/index.js directly without making corresponding changes to src/index.ts. The source file at src/index.ts:900-936 still has the original formatMemoryContext without capHit, injectedCount, or the truncation hint/logging. The next tsc build will overwrite all changes in dist/index.js, reverting this PR entirely.
(Refers to lines 767-801)
Prompt for agents
The changes in this PR were made only to the compiled dist/index.js, not to the TypeScript source file src/index.ts. The source file src/index.ts:900-936 still has the original formatMemoryContext implementation without capHit tracking, injectedCount, the truncation hint line, or the logging.
All the logic changes (capHit flag, injectedCount tracking, the truncation hint message, and the logging) need to be applied to src/index.ts:900-936 in the formatMemoryContext function, and then dist/index.js should be regenerated by running tsc.
Note: when porting these changes to the source, the api.logger.info calls also need to be addressed since formatMemoryContext in the source is also a standalone function without access to api. The function signature will need to accept a logger parameter or the logging should be moved to the call site.
Was this helpful? React with 👍 or 👎 to provide feedback.
| return ""; // Only header, no items fit | ||
| } | ||
| if (capHit) { | ||
| lines.push(`[${injectedCount} of ${relevant.length} memories shown — use cortex_search for more]`); |
There was a problem hiding this comment.
📝 Info: Truncation hint line added after char budget check may exceed maxChars
The new hint line at dist/index.js:798 ([N of M memories shown — use cortex_search for more]) is pushed to lines after the character budget loop exits due to capHit. This line is not counted against maxChars, so the final serialized output could exceed the configured maxInjectionChars budget. Whether this matters depends on how the consuming system handles the injected context — if there's a hard token limit downstream, this could cause unexpected truncation of other context. The serializedLength is logged but not enforced. This is a minor design concern rather than a bug since the hint is small and useful, but worth noting for awareness.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const line = prefix | ||
| ? `- ${prefix} ${item.content}${tag}` | ||
| : `- ${item.content}${tag}`; | ||
| if (charCount + line.length > maxChars) | ||
| if (charCount + line.length > maxChars) { | ||
| capHit = true; | ||
| break; | ||
| } | ||
| lines.push(line); | ||
| charCount += line.length; | ||
| injectedCount++; | ||
| } | ||
| if (lines.length === 1) | ||
| if (lines.length === 1) { | ||
| if (capHit) { | ||
| const serializedLength = lines.join("\n").length; | ||
| api.logger.info(`[cortex] memories-injected=0/${relevant.length} chars=${serializedLength}/${maxChars}`); | ||
| } | ||
| return ""; // Only header, no items fit | ||
| } | ||
| if (capHit) { | ||
| lines.push(`[${injectedCount} of ${relevant.length} memories shown — use cortex_search for more]`); | ||
| const serializedLength = lines.join("\n").length; | ||
| api.logger.info(`[cortex] memories-injected=${injectedCount}/${relevant.length} chars=${serializedLength}/${maxChars}`); | ||
| } | ||
| lines.push("</relevant-memories>"); | ||
| return lines.join("\n"); |
There was a problem hiding this comment.
📝 Info: The </relevant-memories> closing tag is also not counted against maxChars
Both before and after this PR, the </relevant-memories> closing tag (line 802) and the <relevant-memories> opening tag in the header are not counted against charCount/maxChars. This is a pre-existing design choice — only the memory item lines themselves are budgeted. With the new hint line also unbudgeted, the total overhead outside the budget is now: header + hint + closing tag + newlines. This is consistent with the existing approach but worth being aware of if maxInjectionChars is set tightly.
(Refers to lines 765-803)
Was this helpful? React with 👍 or 👎 to provide feedback.
R-369 — Applied all 4 R-368 adversarial review fixesAll 4 issues from the R-368 adversarial review have been addressed in 4 clean commits. No merge — fixes only. Fix 1: Explicit
|
Summary
Add a small log line when
<relevant-memories>injection is truncated bymaxInjectionChars, and append a footer showing that only part of the retrieved set was injected.Closes #5.
Why
This truncation was previously silent, which made it look like mysterious memory loss during recall debugging. The new log line exposes the injected/retrieved count and char usage:
That matches the R-360 audit recommendation to make char-cap truncation visible.
Exact diff preserved from local patch
// Footer: show retrieval count if more were available than injected if (items.length > injectedCount) { + console.info(`[cortex] memories-injected=${injectedCount}/${items.length} chars=${charCount}/${maxChars}`); lines.push(`[${injectedCount} of ${items.length} memories shown — use cortex_search for more]`); }Notes
dist/index.js, so the PR patches that file directlyDivergence from local branch
The installed local patch was in
index.ts; upstream repo currently only hasdist/index.js, so the same logic was replayed there without changing intent.