Skip to content

feat(memory): add Agent Reflection Engine for cross-session learning#565

Open
nikolasdehor wants to merge 4 commits intoSynkraAI:mainfrom
nikolasdehor:feat/agent-reflection-engine
Open

feat(memory): add Agent Reflection Engine for cross-session learning#565
nikolasdehor wants to merge 4 commits intoSynkraAI:mainfrom
nikolasdehor:feat/agent-reflection-engine

Conversation

@nikolasdehor
Copy link
Copy Markdown
Contributor

@nikolasdehor nikolasdehor commented Mar 7, 2026

Resumo

Story 9.6 do Epic 9 (Persistent Memory Layer). Fase 2 do Agent Immortality Protocol (#482).

O Agent Reflection Engine permite que agentes aprendam com execuções passadas e melhorem autonomamente suas estratégias ao longo do tempo.

Funcionalidades

  • Registro de reflexões: Grava resultado, estratégia usada, duração, lições aprendidas
  • Detecção de padrões: Identifica clusters de sucesso/falha por estratégia+taskType
  • Recomendação de estratégias: Antes de tarefas similares, sugere a melhor abordagem baseada em histórico
  • Tracking de tendências: Monitora se performance está melhorando ou declinando por agente
  • Injeção de contexto: Injeta reflexões relevantes automaticamente antes da execução
  • Auto-pruning: Remove reflexões antigas além da janela de retenção

Arquitetura

Segue o mesmo padrão de gotchas-memory.js e decision-memory.js:

  • Extende EventEmitter para integração reativa
  • Persistência em .aiox/reflections.json
  • Enums tipados: Outcome, TaskType, Events
  • Config com defaults sensatos e overrides

Arquivos

Arquivo Descrição
.aios-core/core/memory/reflection-engine.js Módulo principal (~490 linhas)
tests/core/memory/reflection-engine.test.js 44 testes unitários

Cobertura de Testes

  • 44 testes cobrindo: constructor, load/save, recordReflection, getRecommendations, injectContext, pattern detection, trends, prune, stats, listReflections
  • Edge cases: arquivo corrompido, schema version mismatch, campos ausentes, estado vazio

Test plan

  • npx jest tests/core/memory/reflection-engine.test.js — 44/44 passing
  • Integrar com orchestration flow para auto-gravar reflexões após tasks (trabalho futuro, issue separada)
  • Conectar com decision-memory para enriquecer contexto (trabalho futuro, issue separada)

Summary by CodeRabbit

  • New Features
    • Added an agent reflection engine that records reflections, detects patterns, provides strategy recommendations, injects contextual reflections/patterns, offers trends/analytics, pruning, stats, and listing features.
  • Tests
    • Comprehensive test suite covering load/save, recording, recommendations, patterns, trends, pruning, stats, and listing.
  • Chores
    • Updated manifest and entity registry metadata to include the new reflection engine.
  • Bug Fixes
    • Improved module import error reporting to log warnings when optional memory modules fail to load.

Copilot AI review requested due to automatic review settings March 7, 2026 02:17
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 7, 2026

@nikolasdehor is attempting to deploy a commit to the Pedro Valério Lopez's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 7, 2026

Walkthrough

Adds a new ReflectionEngine module for recording, persisting, and reasoning over agent task reflections; updates three modules to destructure GotchasMemory with error-logged require; regenerates manifest metadata and adds extensive tests for the reflection engine.

Changes

Cohort / File(s) Summary
Reflection Engine Core
.aios-core/core/memory/reflection-engine.js, .aiox-core/core/memory/reflection-engine.js
New ReflectionEngine (extends EventEmitter) implementing persistence (load/save to .aiox/reflections.json), reflection recording, pattern detection/upsert, recommendation scoring, context injection, trends/pruning, stats/listing, and exports for ReflectionEngine, Outcome, TaskType, Events, and CONFIG.
Import Pattern Updates
.aiox-core/core/execution/context-injector.js, .aiox-core/core/execution/subagent-dispatcher.js, .aiox-core/core/ideation/ideation-engine.js
Changed GotchasMemory require to destructured form (({ GotchasMemory } = require(...))) inside try/catch; catch now logs a warning including the error message and falls back to null.
Manifest Metadata
.aiox-core/install-manifest.yaml
Updated generated_at, file_count, and per-file checksums/sizes; added new entry for the reflection engine; no structural/top-level path changes.
Entity Registry
.aiox-core/data/entity-registry.yaml
Added reflection-engine entry to the registry and updated checksums/lastVerified and dependencies for affected entities (including execution/context-injector).
Test Suite
tests/core/memory/reflection-engine.test.js
Adds comprehensive tests covering constructor/config, load/save lifecycle, recording/pruning, recommendations, pattern detection, trends, stats/listing, context injection, and event emissions.

Sequence Diagram(s)

sequenceDiagram
    actor Task
    participant RE as ReflectionEngine
    participant Disk as DiskStorage
    participant Pattern as PatternDetector
    participant Rec as Recommender

    Task->>RE: recordReflection(reflection)
    RE->>RE: validate & assign id
    RE->>Disk: save reflections.json (atomic write)
    Disk-->>RE: persisted
    RE->>Pattern: _detectPatterns(newEntry)
    Pattern-->>RE: pattern created/updated
    Task->>RE: getRecommendations(context)
    RE->>Rec: filter & score reflections (outcome, tags, time-decay)
    Rec-->>RE: ranked strategies
    RE->>Task: injectContext(augmentedContext)
    Note over RE: emits REFLECTION_RECORDED, PATTERN_DETECTED, STRATEGY_RECOMMENDED, REFLECTIONS_PRUNED
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Suggested labels

area: core, area: agents, type: test, area: devops

Suggested reviewers

  • Pedrovaleriolopez
🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(memory): add Agent Reflection Engine for cross-session learning' directly and clearly describes the main change: introduction of a new Agent Reflection Engine module for memory persistence and cross-session learning.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an “Agent Reflection Engine” intended to persist execution reflections across sessions and use them to detect patterns, recommend strategies, and inject relevant context before similar tasks—part of the Persistent Memory Layer work for AIP Phase 2.

Changes:

  • Introduces a new ReflectionEngine module with persistence, recommendation, pattern detection, trend tracking, and pruning.
  • Adds a comprehensive Jest unit test suite for the reflection engine.
  • Adjusts GotchasMemory dynamic import style in execution/ideation modules and updates the install manifest accordingly.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
.aios-core/core/memory/reflection-engine.js New reflection engine implementation (persistence, recommendations, patterns, trends, pruning).
tests/core/memory/reflection-engine.test.js New unit tests covering load/save, recording, recommendations, injection, patterns, trends, pruning, stats/listing.
.aiox-core/core/execution/context-injector.js Updates optional GotchasMemory import to destructure the exported constructor; logs warn on failure.
.aiox-core/core/execution/subagent-dispatcher.js Same GotchasMemory import adjustment + warning log on failure.
.aiox-core/core/ideation/ideation-engine.js Same GotchasMemory import adjustment + warning log on failure.
.aiox-core/install-manifest.yaml Regenerated manifest (hash/size updates for modified files and timestamp).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


const pruned = before - this.reflections.length;
if (pruned > 0) {
this.emit(Events.REFLECTIONS_PRUNED, { count: pruned, reason: 'retention_window', removed });
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

prune() removes reflections but does not update/recompute this.patterns, so injected patterns/recommendations may be based on reflections that no longer exist (retention window / max limit). Consider rebuilding patterns after pruning, or pruning patterns whose strategies/taskTypes no longer meet minReflectionsForPattern.

Suggested change
this.emit(Events.REFLECTIONS_PRUNED, { count: pruned, reason: 'retention_window', removed });
this.emit(Events.REFLECTIONS_PRUNED, { count: pruned, reason: 'retention_window', removed });
// Ensure patterns remain consistent with the pruned reflections.
// If patterns are derived from reflections, they are now potentially stale.
if (Array.isArray(this.patterns)) {
this.patterns = [];
}
// If the class provides a rebuild hook, use it to recompute patterns from
// the remaining reflections.
if (typeof this.rebuildPatterns === 'function') {
this.rebuildPatterns();
}

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +16
* Features:
* - AC1: reflection-engine.js in .aios-core/core/memory/
* - AC2: Persists in .aiox/reflections.json
* - AC3: Records execution reflections with outcome, duration, strategy used
* - AC4: Extracts recurring patterns from reflections (success/failure clusters)
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

This new module lives under .aios-core/, but the package publish configuration only includes .aiox-core/ (see package.json files). As-is, reflection-engine.js will not ship to consumers and is also missing from the install-manifest, so the feature will work in tests but not in a packaged install. Move the engine into .aiox-core/core/memory/ (to match other memory modules) or update the publish/install manifests to include .aios-core/.

Copilot uses AI. Check for mistakes.
injectContext(context) {
const recommendations = this.getRecommendations(context);
const relevantPatterns = this.patterns.filter(
(p) => p.taskType === context.taskType || (context.tags && context.tags.some((t) => p.tags.includes(t))),
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

injectContext() assumes every pattern has tags and calls p.tags.includes(...). Since load() does not validate/normalize the persisted patterns array, a missing/invalid tags field in the JSON will throw at runtime during context injection. Consider normalizing patterns on load (default tags to []) and/or guarding p.tags in this filter.

Suggested change
(p) => p.taskType === context.taskType || (context.tags && context.tags.some((t) => p.tags.includes(t))),
(p) =>
p.taskType === context.taskType ||
(Array.isArray(context.tags) &&
context.tags.some((t) => Array.isArray(p.tags) && p.tags.includes(t))),

Copilot uses AI. Check for mistakes.
Comment on lines +174 to +176
durationMs: reflection.durationMs || null,
lesson: reflection.lesson || null,
context: reflection.context || null,
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

recordReflection() uses || when defaulting optional fields (e.g. durationMs: reflection.durationMs || null). This will treat valid falsy values like 0 (and empty strings for lesson/context) as missing and overwrite them. Use nullish coalescing (??) or explicit type checks so 0/'' are preserved when intentionally provided.

Suggested change
durationMs: reflection.durationMs || null,
lesson: reflection.lesson || null,
context: reflection.context || null,
durationMs: reflection.durationMs ?? null,
lesson: reflection.lesson ?? null,
context: reflection.context ?? null,

Copilot uses AI. Check for mistakes.
Comment on lines +182 to +186
// Enforce max reflections
if (this.reflections.length > this.config.maxReflections) {
const removed = this.reflections.shift();
this.emit(Events.REFLECTIONS_PRUNED, { count: 1, reason: 'max_limit', removed: [removed.id] });
}
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

When enforcing maxReflections, an old reflection is removed but this.patterns is left unchanged. This can leave patterns that are no longer supported by the current reflection set (or have incorrect sample sizes). Consider updating/removing affected patterns or rebuilding patterns after any reflection removal to keep patterns consistent with reflections.

Copilot uses AI. Check for mistakes.

const fs = require('fs');
const path = require('path');
const ReflectionEngine = require('../../../.aios-core/core/memory/reflection-engine');
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

The tests import the engine from ../../../.aios-core/..., which will pass in-repo but will break for consumers if .aios-core/ is not shipped (it currently isn’t in package.json files). If the engine is moved into .aiox-core/, update this import path accordingly to avoid tests masking a packaging/runtime issue.

Suggested change
const ReflectionEngine = require('../../../.aios-core/core/memory/reflection-engine');
const ReflectionEngine = require('../../../.aiox-core/core/memory/reflection-engine');

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@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: 5

🧹 Nitpick comments (1)
.aiox-core/core/ideation/ideation-engine.js (1)

16-16: Use the repo's absolute module path here.

This touched require() still uses a relative import, and the same pattern is repeated in the other updated core loaders.

As per coding guidelines, "**/*.{js,jsx,ts,tsx}: Use absolute imports instead of relative imports in all code`".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/core/ideation/ideation-engine.js at line 16, The code in
ideation-engine.js imports GotchasMemory with a relative require
(require('../memory/gotchas-memory')); replace that with the repo's canonical
absolute module import for GotchasMemory (follow the project's absolute-import
convention) so the GotchasMemory symbol is imported via the absolute path rather
than a relative one; update the require call accordingly and mirror the same
change pattern in the other updated core loaders that use similar relative
imports.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.aios-core/core/memory/reflection-engine.js:
- Around line 100-122: The class currently sets this._loaded in load() but does
not enforce it, allowing public methods like recordReflection() and save() to
run before load() and clobber disk state; add a small initialization guard:
implement a private async method (e.g., _ensureLoaded()) that checks
this._loaded and if false awaits this.load() (or throws if you prefer strict
behavior), and call _ensureLoaded() at the start of all public read/write APIs
(including recordReflection(), save(), and the other public methods referenced
around lines 127-144 and 161-193) so operations either lazy-load the file first
or fail until load completes. Ensure load() remains idempotent and sets
this._loaded = true on success and leaves it false on failure so the guard
behaves correctly.
- Around line 289-303: injectContext(context) in ReflectionEngine is only
exercised by tests because the runtime context-injector doesn't call it; wire
the reflection engine into the execution-side context construction so production
contexts get reflections. Import or obtain the ReflectionEngine instance in the
execution/context-injector (or the module that builds runtime context), then
call ReflectionEngine.injectContext(context) (using the existing injectContext
method name) and return that enriched context instead of the current plain
context; ensure this runs after MemoryQuery/GotchasMemory/SessionMemory are
initialized so reflections can use those memories.
- Around line 183-186: When a reflection is evicted (e.g., the shift in the
block referencing this.reflections and this.config.maxReflections and the other
removal path around the 367-382 block), you must also remove or rebuild any
entries in this.patterns that reference the removed reflection(s) so
injectContext() can't use stale pattern data; capture the removed reflection
id(s) (the removed variable/array) and either call a new helper (e.g.,
prunePatternsByReflectionIds(removedIds)) or inline-filter this.patterns to
exclude patterns whose source reflection id matches any removed id, then
continue to emit Events.REFLECTIONS_PRUNED with the same metadata.
- Around line 1-24: The new module header "AIOX Agent Reflection Engine" was
added under .aios-core but the runtime tree and install manifest expect modules
under .aiox-core, so move the file reflection-engine.js into the packaged core
tree (the core/memory path under .aiox-core) and update the packaging/install
manifest to include core/memory/reflection-engine.js; ensure any build/package
config or index that enumerates core modules (the manifest and any module
registry used at runtime) references the new location and that the file retains
its shebang and top-level metadata so the reflection engine is included in
brownfield installs/upgrades.
- Around line 127-143: save() currently overwrites the shared JSON file causing
last-writer-wins data loss; change save() (and use _getFilePath, reflections,
patterns) to perform a read-merge-write with an atomic write and simple
concurrency control: before writing, read the on-disk file (if exists), parse
and merge its reflections/patterns with this.reflections/this.patterns using a
deterministic dedupe key (e.g., id or timestamp), then write the merged object
to a temp file and atomically rename to the target path (fs.writeFileSync temp +
fs.renameSync) to avoid partial writes; additionally implement a retry loop that
re-reads and re-merges if the on-disk file changed between read and rename (or
use an advisory file lock if available) so concurrent saves converge instead of
clobbering.

---

Nitpick comments:
In @.aiox-core/core/ideation/ideation-engine.js:
- Line 16: The code in ideation-engine.js imports GotchasMemory with a relative
require (require('../memory/gotchas-memory')); replace that with the repo's
canonical absolute module import for GotchasMemory (follow the project's
absolute-import convention) so the GotchasMemory symbol is imported via the
absolute path rather than a relative one; update the require call accordingly
and mirror the same change pattern in the other updated core loaders that use
similar relative imports.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0b132907-0adb-4c8e-b6d0-deb038fcd06c

📥 Commits

Reviewing files that changed from the base of the PR and between fcfb757 and 3938e8e.

📒 Files selected for processing (6)
  • .aios-core/core/memory/reflection-engine.js
  • .aiox-core/core/execution/context-injector.js
  • .aiox-core/core/execution/subagent-dispatcher.js
  • .aiox-core/core/ideation/ideation-engine.js
  • .aiox-core/install-manifest.yaml
  • tests/core/memory/reflection-engine.test.js

Comment on lines +100 to +122
async load() {
const filePath = this._getFilePath();
try {
if (fs.existsSync(filePath)) {
const raw = fs.readFileSync(filePath, 'utf8');
const data = JSON.parse(raw);

if (data.schemaVersion !== this.config.schemaVersion) {
this.reflections = [];
this.patterns = [];
this._loaded = true;
return;
}

this.reflections = Array.isArray(data.reflections) ? data.reflections : [];
this.patterns = Array.isArray(data.patterns) ? data.patterns : [];
}
} catch {
this.reflections = [];
this.patterns = [];
}
this._loaded = true;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Require initialization before any public read/write API runs.

_loaded is set in load() but never enforced. A fresh process can call recordReflection(); save() without loading the existing file first, and save() will overwrite the on-disk store with only the new in-memory state. Either throw until load() completes or lazy-load inside the public methods.

Also applies to: 127-144, 161-193

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/memory/reflection-engine.js around lines 100 - 122, The
class currently sets this._loaded in load() but does not enforce it, allowing
public methods like recordReflection() and save() to run before load() and
clobber disk state; add a small initialization guard: implement a private async
method (e.g., _ensureLoaded()) that checks this._loaded and if false awaits
this.load() (or throws if you prefer strict behavior), and call _ensureLoaded()
at the start of all public read/write APIs (including recordReflection(),
save(), and the other public methods referenced around lines 127-144 and
161-193) so operations either lazy-load the file first or fail until load
completes. Ensure load() remains idempotent and sets this._loaded = true on
success and leaves it false on failure so the guard behaves correctly.

Comment on lines +289 to +303
injectContext(context) {
const recommendations = this.getRecommendations(context);
const relevantPatterns = this.patterns.filter(
(p) => p.taskType === context.taskType || (context.tags && context.tags.some((t) => p.tags.includes(t))),
);

return {
...context,
reflections: {
recommendations,
patterns: relevantPatterns,
totalReflections: this.reflections.filter((r) => r.taskType === context.taskType).length,
},
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

injectContext() still has no production caller.

The execution-side context injector shown in .aiox-core/core/execution/context-injector.js still only initializes MemoryQuery, GotchasMemory, and SessionMemory, so this method is currently exercised only by unit tests. AC7 is not actually delivered until the runtime path calls it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/memory/reflection-engine.js around lines 289 - 303,
injectContext(context) in ReflectionEngine is only exercised by tests because
the runtime context-injector doesn't call it; wire the reflection engine into
the execution-side context construction so production contexts get reflections.
Import or obtain the ReflectionEngine instance in the execution/context-injector
(or the module that builds runtime context), then call
ReflectionEngine.injectContext(context) (using the existing injectContext method
name) and return that enriched context instead of the current plain context;
ensure this runs after MemoryQuery/GotchasMemory/SessionMemory are initialized
so reflections can use those memories.

Copy link
Copy Markdown

@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: 5

🧹 Nitpick comments (3)
.aios-core/core/memory/reflection-engine.js (3)

500-503: Same forEach issue as _detectPatterns.

Apply the same fix using for...of for consistency.

♻️ Proposed fix
       const tagSet = new Set();
       for (const r of group) {
-        if (r.tags) r.tags.forEach((t) => tagSet.add(t));
+        if (r.tags) {
+          for (const t of r.tags) tagSet.add(t);
+        }
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/memory/reflection-engine.js around lines 500 - 503, The
block building tagSet uses r.tags.forEach which can be replaced for consistency
and safety with a for...of loop: in the same area as the group iteration (where
tagSet, group, and r.tags are used, analogous to the _detectPatterns change),
iterate over r.tags using for (const t of r.tags) { tagSet.add(t); } and remove
the forEach call so tag collection uses a for...of pattern consistently.

296-303: Consider naming clarity for nested patterns key.

The returned context.reflections.patterns contains reflection-derived patterns. The master-orchestrator also injects a top-level patterns key with execution insights. While not a direct conflict (different nesting), consumers may need clarity on which patterns to use.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/memory/reflection-engine.js around lines 296 - 303, The
nested key context.reflections.patterns is ambiguous with the top-level patterns
injected elsewhere; rename that property to something explicit (e.g.,
reflectionPatterns or patternsFromReflections) in the object returned by the
function that builds the reflections block (the return that currently spreads
...context and sets reflections: { recommendations, patterns: relevantPatterns,
totalReflections: ... }) and update any internal usages that read
context.reflections.patterns to the new name (also update tests and docs
referencing this shape) so callers can clearly distinguish reflection-derived
patterns from the top-level execution insights patterns.

446-450: forEach callback should not return a value.

tagSet.add(t) returns the Set, which forEach ignores. While harmless, this triggers a lint warning and for...of is cleaner.

♻️ Proposed fix
     const tagSet = new Set();
     for (const r of all) {
-      if (r.tags) r.tags.forEach((t) => tagSet.add(t));
+      if (r.tags) {
+        for (const t of r.tags) tagSet.add(t);
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/memory/reflection-engine.js around lines 446 - 450, The lint
warning comes from using r.tags.forEach((t) => tagSet.add(t)) which returns the
Set inside the callback; change this to iterate without returning a value—e.g.,
replace the forEach with a for...of loop over r.tags (for (const t of r.tags)
tagSet.add(t);) so the callback does not return anything and the code is
cleaner; update the loop that populates tagSet (variables: tagSet, all, r,
r.tags) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.aios-core/core/memory/reflection-engine.js:
- Around line 107-112: The code silently discards on-disk data when
data.schemaVersion !== this.config.schemaVersion by immediately clearing
this.reflections, this.patterns and setting this._loaded; modify the mismatch
handling in the function containing that check to (1) log or emit a warning
including both data.schemaVersion and this.config.schemaVersion, (2) persist or
move the existing data to a backup location or expose it via an event before
clearing, and (3) only reset this.reflections/this.patterns after the backup
succeeds (or provide a configurable policy to preserve vs reset); update the
branch where data.schemaVersion !== this.config.schemaVersion to call your
logger/backup utilities and include those version values in the message so the
reset is explicit and recoverable.
- Around line 117-120: The empty catch swallows errors when loading reflections;
change it to catch the error (e.g., catch (err)) and log the failure before
resetting state: call the existing logger (or console.error) with a clear
message and the error object, then set this.reflections = [] and this.patterns =
[]; ensure the catch references the same symbols shown (this.reflections,
this.patterns) so the error is recorded for debugging.
- Line 143: fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8')
can throw on I/O errors; wrap this call in a try/catch (around the
fs.writeFileSync invocation that uses filePath and data) and handle errors
appropriately: catch the exception, log a clear error message including filePath
and the error object via your module logger (or console.error if no logger), and
either rethrow a wrapped error or return a controlled failure value so it
doesn’t crash the process unexpectedly; ensure the catch does not swallow the
error silently.

In @.aiox-core/core/memory/reflection-engine.js:
- Around line 127-144: The save() method uses blocking fs.mkdirSync and
fs.writeFileSync inside an async function; replace these with the non-blocking
fs.promises equivalents: await fs.promises.mkdir(dir, { recursive: true }) and
await fs.promises.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8'),
keeping the same data object (schemaVersion, version, savedAt, reflections,
patterns) and using this._getFilePath() to locate file; ensure save() awaits the
async calls and properly propagates or catches errors as appropriate.
- Around line 100-122: The load() method is marked async but does synchronous fs
I/O and swallows errors; change load() to use non-blocking fs.promises (e.g.,
fs.promises.access or fs.promises.readFile) to check existence and read the file
asynchronously via this._getFilePath(), then JSON.parse the result and retain
the existing schemaVersion check and assignments to this.reflections and
this.patterns (fall back to [] if not arrays); replace the empty catch block
with one that logs or rethrows the caught error (include context: filePath,
config.schemaVersion) and still ensure this._loaded is set to true at the end so
callers relying on that flag behave correctly.

---

Nitpick comments:
In @.aios-core/core/memory/reflection-engine.js:
- Around line 500-503: The block building tagSet uses r.tags.forEach which can
be replaced for consistency and safety with a for...of loop: in the same area as
the group iteration (where tagSet, group, and r.tags are used, analogous to the
_detectPatterns change), iterate over r.tags using for (const t of r.tags) {
tagSet.add(t); } and remove the forEach call so tag collection uses a for...of
pattern consistently.
- Around line 296-303: The nested key context.reflections.patterns is ambiguous
with the top-level patterns injected elsewhere; rename that property to
something explicit (e.g., reflectionPatterns or patternsFromReflections) in the
object returned by the function that builds the reflections block (the return
that currently spreads ...context and sets reflections: { recommendations,
patterns: relevantPatterns, totalReflections: ... }) and update any internal
usages that read context.reflections.patterns to the new name (also update tests
and docs referencing this shape) so callers can clearly distinguish
reflection-derived patterns from the top-level execution insights patterns.
- Around line 446-450: The lint warning comes from using r.tags.forEach((t) =>
tagSet.add(t)) which returns the Set inside the callback; change this to iterate
without returning a value—e.g., replace the forEach with a for...of loop over
r.tags (for (const t of r.tags) tagSet.add(t);) so the callback does not return
anything and the code is cleaner; update the loop that populates tagSet
(variables: tagSet, all, r, r.tags) accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6df03a6d-e81f-4b18-8cf1-b0ff953d4856

📥 Commits

Reviewing files that changed from the base of the PR and between 3938e8e and e3332b1.

📒 Files selected for processing (2)
  • .aios-core/core/memory/reflection-engine.js
  • .aiox-core/core/memory/reflection-engine.js

Comment on lines +107 to +112
if (data.schemaVersion !== this.config.schemaVersion) {
this.reflections = [];
this.patterns = [];
this._loaded = true;
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Schema version mismatch resets data without notification.

When the on-disk schema version differs from the expected version, data is silently discarded. This could cause confusion during upgrades.

🛠️ Proposed fix
         if (data.schemaVersion !== this.config.schemaVersion) {
+          console.warn(
+            `[ReflectionEngine] Schema mismatch: found '${data.schemaVersion}', expected '${this.config.schemaVersion}'. Resetting reflections.`
+          );
           this.reflections = [];
           this.patterns = [];
           this._loaded = true;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (data.schemaVersion !== this.config.schemaVersion) {
this.reflections = [];
this.patterns = [];
this._loaded = true;
return;
}
if (data.schemaVersion !== this.config.schemaVersion) {
console.warn(
`[ReflectionEngine] Schema mismatch: found '${data.schemaVersion}', expected '${this.config.schemaVersion}'. Resetting reflections.`
);
this.reflections = [];
this.patterns = [];
this._loaded = true;
return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/memory/reflection-engine.js around lines 107 - 112, The code
silently discards on-disk data when data.schemaVersion !==
this.config.schemaVersion by immediately clearing this.reflections,
this.patterns and setting this._loaded; modify the mismatch handling in the
function containing that check to (1) log or emit a warning including both
data.schemaVersion and this.config.schemaVersion, (2) persist or move the
existing data to a backup location or expose it via an event before clearing,
and (3) only reset this.reflections/this.patterns after the backup succeeds (or
provide a configurable policy to preserve vs reset); update the branch where
data.schemaVersion !== this.config.schemaVersion to call your logger/backup
utilities and include those version values in the message so the reset is
explicit and recoverable.

Comment on lines +100 to +122
async load() {
const filePath = this._getFilePath();
try {
if (fs.existsSync(filePath)) {
const raw = fs.readFileSync(filePath, 'utf8');
const data = JSON.parse(raw);

if (data.schemaVersion !== this.config.schemaVersion) {
this.reflections = [];
this.patterns = [];
this._loaded = true;
return;
}

this.reflections = Array.isArray(data.reflections) ? data.reflections : [];
this.patterns = Array.isArray(data.patterns) ? data.patterns : [];
}
} catch {
this.reflections = [];
this.patterns = [];
}
this._loaded = true;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Misleading async signature with synchronous file I/O.

The load() method is marked async but uses synchronous fs.existsSync and fs.readFileSync. This is misleading to callers who expect non-blocking behavior and can block the event loop under load.

Additionally, the empty catch {} block at line 117 silently swallows errors, losing valuable diagnostic information when file parsing fails.

🔧 Proposed fix using async fs operations
   async load() {
     const filePath = this._getFilePath();
     try {
-      if (fs.existsSync(filePath)) {
-        const raw = fs.readFileSync(filePath, 'utf8');
+      const raw = await fs.promises.readFile(filePath, 'utf8');
-        const data = JSON.parse(raw);
+      const data = JSON.parse(raw);
 
-        if (data.schemaVersion !== this.config.schemaVersion) {
-          this.reflections = [];
-          this.patterns = [];
-          this._loaded = true;
-          return;
-        }
+      if (data.schemaVersion !== this.config.schemaVersion) {
+        this.reflections = [];
+        this.patterns = [];
+        this._loaded = true;
+        return;
+      }
 
-        this.reflections = Array.isArray(data.reflections) ? data.reflections : [];
-        this.patterns = Array.isArray(data.patterns) ? data.patterns : [];
-      }
-    } catch {
+      this.reflections = Array.isArray(data.reflections) ? data.reflections : [];
+      this.patterns = Array.isArray(data.patterns) ? data.patterns : [];
+    } catch (err) {
+      if (err.code !== 'ENOENT') {
+        console.warn(`[ReflectionEngine] Failed to load reflections: ${err.message}`);
+      }
       this.reflections = [];
       this.patterns = [];
     }
     this._loaded = true;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async load() {
const filePath = this._getFilePath();
try {
if (fs.existsSync(filePath)) {
const raw = fs.readFileSync(filePath, 'utf8');
const data = JSON.parse(raw);
if (data.schemaVersion !== this.config.schemaVersion) {
this.reflections = [];
this.patterns = [];
this._loaded = true;
return;
}
this.reflections = Array.isArray(data.reflections) ? data.reflections : [];
this.patterns = Array.isArray(data.patterns) ? data.patterns : [];
}
} catch {
this.reflections = [];
this.patterns = [];
}
this._loaded = true;
}
async load() {
const filePath = this._getFilePath();
try {
const raw = await fs.promises.readFile(filePath, 'utf8');
const data = JSON.parse(raw);
if (data.schemaVersion !== this.config.schemaVersion) {
this.reflections = [];
this.patterns = [];
this._loaded = true;
return;
}
this.reflections = Array.isArray(data.reflections) ? data.reflections : [];
this.patterns = Array.isArray(data.patterns) ? data.patterns : [];
} catch (err) {
if (err.code !== 'ENOENT') {
console.warn(`[ReflectionEngine] Failed to load reflections: ${err.message}`);
}
this.reflections = [];
this.patterns = [];
}
this._loaded = true;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/core/memory/reflection-engine.js around lines 100 - 122, The
load() method is marked async but does synchronous fs I/O and swallows errors;
change load() to use non-blocking fs.promises (e.g., fs.promises.access or
fs.promises.readFile) to check existence and read the file asynchronously via
this._getFilePath(), then JSON.parse the result and retain the existing
schemaVersion check and assignments to this.reflections and this.patterns (fall
back to [] if not arrays); replace the empty catch block with one that logs or
rethrows the caught error (include context: filePath, config.schemaVersion) and
still ensure this._loaded is set to true at the end so callers relying on that
flag behave correctly.

Comment on lines +127 to +144
async save() {
const filePath = this._getFilePath();
const dir = path.dirname(filePath);

if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}

const data = {
schemaVersion: this.config.schemaVersion,
version: this.config.version,
savedAt: new Date().toISOString(),
reflections: this.reflections,
patterns: this.patterns,
};

fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Synchronous file writes in async method.

Similar to load(), the save() method is marked async but uses synchronous fs.mkdirSync and fs.writeFileSync. This blocks the event loop during writes.

🔧 Proposed fix using async fs operations
   async save() {
     const filePath = this._getFilePath();
     const dir = path.dirname(filePath);
 
-    if (!fs.existsSync(dir)) {
-      fs.mkdirSync(dir, { recursive: true });
-    }
+    await fs.promises.mkdir(dir, { recursive: true });
 
     const data = {
       schemaVersion: this.config.schemaVersion,
       version: this.config.version,
       savedAt: new Date().toISOString(),
       reflections: this.reflections,
       patterns: this.patterns,
     };
 
-    fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
+    await fs.promises.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8');
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/core/memory/reflection-engine.js around lines 127 - 144, The
save() method uses blocking fs.mkdirSync and fs.writeFileSync inside an async
function; replace these with the non-blocking fs.promises equivalents: await
fs.promises.mkdir(dir, { recursive: true }) and await
fs.promises.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8'), keeping
the same data object (schemaVersion, version, savedAt, reflections, patterns)
and using this._getFilePath() to locate file; ensure save() awaits the async
calls and properly propagates or catches errors as appropriate.

@nikolasdehor nikolasdehor force-pushed the feat/agent-reflection-engine branch from e3332b1 to 9fedd30 Compare March 11, 2026 02:24
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 11, 2026

Codecov Report

❌ Patch coverage is 0% with 207 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
.aiox-core/core/memory/reflection-engine.js 0.00% 155 Missing and 50 partials ⚠️
.aiox-core/core/ideation/ideation-engine.js 0.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@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: 2

🧹 Nitpick comments (5)
.aios-core/core/memory/reflection-engine.js (3)

447-450: Static analysis: forEach callback should not return a value.

The add() method returns the Set, but forEach expects callbacks to return nothing. While harmless here, using for...of is cleaner.

♻️ Suggested fix
     const tagSet = new Set();
     for (const r of all) {
-      if (r.tags) r.tags.forEach((t) => tagSet.add(t));
+      if (r.tags) {
+        for (const t of r.tags) tagSet.add(t);
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/memory/reflection-engine.js around lines 447 - 450, The code
uses r.tags.forEach((t) => tagSet.add(t)) where Array.prototype.forEach
callbacks should not return values; replace this with an explicit loop to avoid
relying on add()'s return and make intent clearer: iterate over all (the
variable all) and for each r where r.tags exists, use a for...of (or simple for)
over r.tags and call tagSet.add(t) for each t; keep the tagSet and r variables
unchanged so you only swap the forEach to an explicit iteration.

500-503: Static analysis: Same forEach callback return value issue.

Same as line 449 - use for...of for cleaner iteration.

♻️ Suggested fix
       const tagSet = new Set();
       for (const r of group) {
-        if (r.tags) r.tags.forEach((t) => tagSet.add(t));
+        if (r.tags) {
+          for (const t of r.tags) tagSet.add(t);
+        }
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/memory/reflection-engine.js around lines 500 - 503, The loop
building tagSet uses Array.prototype.forEach callback (r.tags.forEach((t) =>
tagSet.add(t))) which can be replaced with a direct for...of to avoid relying on
forEach return/behavior; change the inner iteration over r.tags to: for (const t
of r.tags) tagSet.add(t), keeping the outer loop over group as-is (references:
tagSet, group, r.tags).

333-348: Edge case: Trend detection with very few reflections may produce misleading results.

When relevant.length is 1-2, the half-split logic produces imbalanced or empty halves:

  • With 1 reflection: firstHalf = [], secondHalf = [1 item]firstRate = 0, secondRate = 0 or 1
  • This can falsely indicate "improving" or "declining" based on a single data point.

Consider adding a minimum sample size check before computing trend direction.

💡 Suggested improvement
     let trend = 'stable';
-    if (secondRate - firstRate > 0.15) trend = 'improving';
-    else if (firstRate - secondRate > 0.15) trend = 'declining';
+    // Require minimum samples in each half for meaningful trend
+    if (firstHalf.length >= 2 && secondHalf.length >= 2) {
+      if (secondRate - firstRate > 0.15) trend = 'improving';
+      else if (firstRate - secondRate > 0.15) trend = 'declining';
+    } else {
+      trend = 'insufficient_data';
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/memory/reflection-engine.js around lines 333 - 348, The
trend calculation using the mid-split (variables mid, firstHalf, secondHalf,
firstRate, secondRate, trend) can be misleading for very small relevant arrays;
add a minimum sample-size guard (e.g., if relevant.length < 4) before computing
firstRate/secondRate and set trend to 'stable' or 'insufficient' when the sample
is too small, otherwise proceed with the current split and threshold logic;
update the code that computes trend (the block that sets firstRate, secondRate
and evaluates secondRate - firstRate) to early-return or short-circuit when
relevant.length is below the chosen minimum.
.aiox-core/core/memory/reflection-engine.js (2)

80-88: Consider documenting thread-safety assumptions.

The class maintains in-memory state (reflections, patterns) without synchronization primitives. This is acceptable for single-threaded Node.js usage, but a brief comment noting that concurrent access from multiple orchestration flows could lead to race conditions would help future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/core/memory/reflection-engine.js around lines 80 - 88, Add a
brief comment in the ReflectionEngine class explaining the thread-safety
assumptions: note that the in-memory state fields (reflections, patterns,
_loaded) are not synchronized and are safe under Node.js single-threaded event
loop usage, and warn that concurrent access from worker threads or external
orchestration flows could cause race conditions and would require
synchronization or moving state to a thread-safe store; place this comment near
the ReflectionEngine constructor to make it discoverable.

413-424: List returns most recent reflections by design.

The slice(-filter.limit) returns the tail of the array, which corresponds to the most recent reflections given chronological ordering. This behavior is useful but could benefit from a brief doc comment clarifying the semantics.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/core/memory/reflection-engine.js around lines 413 - 424, The
listReflections method uses slice(-filter.limit) to return the tail of the
chronological reflections array (i.e., the most recent reflections) when a limit
is provided; add a brief doc comment above the listReflections function
explaining that the reflections array is ordered chronologically and that
filter.limit returns the most recent N reflections (not the first N), and
mention the semantics for other filter keys (taskType, agentId, outcome, tag) so
callers understand ordering and filtering behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.aios-core/core/memory/reflection-engine.js:
- Around line 1-24: The CI failure is due to the new reflection-engine.js module
not being registered in the project's manifest; run the manifest generator (npm
run generate:manifest) to regenerate and include reflection-engine.js (the "AIOX
Agent Reflection Engine" module), verify the manifest now lists
reflection-engine.js, and commit the updated manifest so the module is included
in installs/upgrades.

In @.aiox-core/install-manifest.yaml:
- Around line 11-14: The install manifest is missing the auto-generated entry
for core/memory/reflection-engine.js; run the manifest regeneration using the
generator (run: npm run generate:manifest or execute
scripts/generate-install-manifest.js) so that .aiox-core/install-manifest.yaml
includes "core/memory/reflection-engine.js" in the files list and updates
generated_at/file_count accordingly, then commit the updated manifest.

---

Nitpick comments:
In @.aios-core/core/memory/reflection-engine.js:
- Around line 447-450: The code uses r.tags.forEach((t) => tagSet.add(t)) where
Array.prototype.forEach callbacks should not return values; replace this with an
explicit loop to avoid relying on add()'s return and make intent clearer:
iterate over all (the variable all) and for each r where r.tags exists, use a
for...of (or simple for) over r.tags and call tagSet.add(t) for each t; keep the
tagSet and r variables unchanged so you only swap the forEach to an explicit
iteration.
- Around line 500-503: The loop building tagSet uses Array.prototype.forEach
callback (r.tags.forEach((t) => tagSet.add(t))) which can be replaced with a
direct for...of to avoid relying on forEach return/behavior; change the inner
iteration over r.tags to: for (const t of r.tags) tagSet.add(t), keeping the
outer loop over group as-is (references: tagSet, group, r.tags).
- Around line 333-348: The trend calculation using the mid-split (variables mid,
firstHalf, secondHalf, firstRate, secondRate, trend) can be misleading for very
small relevant arrays; add a minimum sample-size guard (e.g., if relevant.length
< 4) before computing firstRate/secondRate and set trend to 'stable' or
'insufficient' when the sample is too small, otherwise proceed with the current
split and threshold logic; update the code that computes trend (the block that
sets firstRate, secondRate and evaluates secondRate - firstRate) to early-return
or short-circuit when relevant.length is below the chosen minimum.

In @.aiox-core/core/memory/reflection-engine.js:
- Around line 80-88: Add a brief comment in the ReflectionEngine class
explaining the thread-safety assumptions: note that the in-memory state fields
(reflections, patterns, _loaded) are not synchronized and are safe under Node.js
single-threaded event loop usage, and warn that concurrent access from worker
threads or external orchestration flows could cause race conditions and would
require synchronization or moving state to a thread-safe store; place this
comment near the ReflectionEngine constructor to make it discoverable.
- Around line 413-424: The listReflections method uses slice(-filter.limit) to
return the tail of the chronological reflections array (i.e., the most recent
reflections) when a limit is provided; add a brief doc comment above the
listReflections function explaining that the reflections array is ordered
chronologically and that filter.limit returns the most recent N reflections (not
the first N), and mention the semantics for other filter keys (taskType,
agentId, outcome, tag) so callers understand ordering and filtering behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4958adff-17ec-4e4e-95db-9826db65434e

📥 Commits

Reviewing files that changed from the base of the PR and between e3332b1 and 9fedd30.

📒 Files selected for processing (7)
  • .aios-core/core/memory/reflection-engine.js
  • .aiox-core/core/execution/context-injector.js
  • .aiox-core/core/execution/subagent-dispatcher.js
  • .aiox-core/core/ideation/ideation-engine.js
  • .aiox-core/core/memory/reflection-engine.js
  • .aiox-core/install-manifest.yaml
  • tests/core/memory/reflection-engine.test.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • .aiox-core/core/execution/context-injector.js
  • tests/core/memory/reflection-engine.test.js
  • .aiox-core/core/execution/subagent-dispatcher.js

Comment on lines +1 to +24
#!/usr/bin/env node

/**
* AIOX Agent Reflection Engine
*
* Story: 9.6 - Agent Reflection Engine
* Epic: Epic 9 - Persistent Memory Layer
*
* Enables agents to reflect on past executions, extract lessons,
* and autonomously improve their strategies over time.
*
* Features:
* - AC1: reflection-engine.js in .aios-core/core/memory/
* - AC2: Persists in .aiox/reflections.json
* - AC3: Records execution reflections with outcome, duration, strategy used
* - AC4: Extracts recurring patterns from reflections (success/failure clusters)
* - AC5: Recommends strategies before similar tasks based on historical outcomes
* - AC6: Tracks performance trends per agent, task type, and strategy
* - AC7: Injects relevant reflections as context before task execution
* - AC8: Prunes stale reflections beyond retention window
*
* @author @dev (Dex)
* @version 1.0.0
*/
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Pipeline failure: Manifest is outdated and missing this file.

The CI pipeline is failing because core/memory/reflection-engine.js is not registered in the manifest. This will prevent the module from being included in brownfield installs/upgrades.

Run npm run generate:manifest to update the manifest and include this new file.

🧰 Tools
🪛 GitHub Actions: CI

[error] 1-1: Manifest is OUTDATED. A new file 'core/memory/reflection-engine.js' is not in the manifest. To fix, run 'npm run generate:manifest'.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/memory/reflection-engine.js around lines 1 - 24, The CI
failure is due to the new reflection-engine.js module not being registered in
the project's manifest; run the manifest generator (npm run generate:manifest)
to regenerate and include reflection-engine.js (the "AIOX Agent Reflection
Engine" module), verify the manifest now lists reflection-engine.js, and commit
the updated manifest so the module is included in installs/upgrades.

gotchas-memory.js exporta named export { GotchasMemory, ... }, mas
ideation-engine.js atribuía o objeto module inteiro à variável.
Resultado: new GotchasMemory() lançava TypeError silenciado pelo
try/catch, e gotchasMemory ficava sempre null.

Corrige com destructuring: ({ GotchasMemory } = require(...))

Closes SynkraAI#517
Story 9.6 of Epic 9 (Persistent Memory Layer).
Phase 2 of Agent Immortality Protocol (SynkraAI#482).

The Reflection Engine enables agents to learn from past executions:
- Record execution reflections with outcome, strategy, duration
- Extract recurring patterns (success/failure clusters)
- Recommend strategies before similar tasks based on history
- Track performance trends per agent and task type
- Inject relevant reflections as context before execution
- Auto-prune stale reflections beyond retention window

44 unit tests covering all features including edge cases.
- Use nullish coalescing (??) instead of || for durationMs, lesson,
  context fields to correctly preserve falsy values like 0
- Add null check for p.tags in injectContext() to prevent TypeError
  when patterns lack tags
- Add _recomputePatterns() method that rebuilds patterns from remaining
  reflections after pruning
- Call _recomputePatterns() in prune() and after maxReflections eviction
  to keep patterns consistent with remaining data
- Copy reflection-engine.js to .aiox-core/core/memory/ for rebranded
  package path compatibility (keeps .aios-core version too)
- _ensureLoaded() para lazy-load e prevenir overwrite de estado persistido
- save() com guarda de concorrencia, escrita atomica (tmp+rename) e tratamento de erro
- load() loga warnings em vez de engolir erros silenciosamente
- Schema mismatch agora loga aviso antes de resetar dados
- injectContext() guarda p.tags com Array.isArray contra dados persistidos malformados
- recordReflection usa ?? em vez de || para preservar valores falsy validos (0, '')
- listReflections guarda r.tags com Array.isArray
- getStats e getTrends usam ?? em vez de || para defaults
- JSDoc adicionado nos metodos publicos e privados
@nikolasdehor nikolasdehor force-pushed the feat/agent-reflection-engine branch from 9fedd30 to 1899e9d Compare March 17, 2026 15:40
@github-actions github-actions bot added area: agents Agent system related area: workflows Workflow system related squad mcp type: test Test coverage and quality area: core Core framework (.aios-core/core/) area: installer Installer and setup (packages/installer/) area: synapse SYNAPSE context engine area: cli CLI tools (bin/, packages/aios-pro-cli/) labels Mar 17, 2026
@github-actions github-actions bot added area: pro Pro features (pro/) area: health-check Health check system area: docs Documentation (docs/) area: devops CI/CD, GitHub Actions (.github/) labels Mar 17, 2026
Copy link
Copy Markdown

@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: 7

Caution

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

⚠️ Outside diff range comments (1)
.aiox-core/data/entity-registry.yaml (1)

8732-8738: ⚠️ Potential issue | 🟡 Minor

Keep actual and planned dependencies mutually exclusive.

These entries now say the same dependencies are both shipped and still planned, which makes the registry graph contradictory for downstream tooling. Please fix the generator/regeneration step so promoted dependencies are removed from plannedDeps.

Suggested registry state
      dependencies:
        - memory-query
        - gotchas-memory
        - session-memory
      externalDeps: []
      plannedDeps:
-        - memory-query
-        - session-memory
      dependencies:
        - ai-providers
        - memory-query
        - gotchas-memory
      externalDeps: []
      plannedDeps:
-        - ai-providers
-        - memory-query

Also applies to: 8856-8862

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/data/entity-registry.yaml around lines 8732 - 8738, The registry
entries currently list the same modules (e.g., memory-query, gotchas-memory,
session-memory) under both shipped/externalDeps and plannedDeps; update the
generator/regeneration logic to ensure promoted/shipped dependencies are removed
from plannedDeps. Concretely, in the generator step that builds the plannedDeps
list, subtract any dependency present in externalDeps (or the shipped
dependencies set) from plannedDeps so they are mutually exclusive; ensure the
code that produces the YAML for keys plannedDeps and externalDeps reflects that
difference and add a test that regenerating the registry does not produce
overlapping entries for names like memory-query, gotchas-memory, session-memory.
♻️ Duplicate comments (2)
.aiox-core/core/memory/reflection-engine.js (1)

100-120: ⚠️ Potential issue | 🟠 Major

load()/save() are async but perform blocking sync I/O with weak error surfacing.

This keeps disk operations on the event loop and load() drops error context (Line 117-Line 120). In this core path, prefer non-blocking fs APIs and explicit error context.

As per coding guidelines "Validate async/await patterns and ensure no unhandled promise rejections." and "Verify error handling is comprehensive with proper try/catch and error context."

Also applies to: 127-144

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/core/memory/reflection-engine.js around lines 100 - 120, The
load() and save() methods currently use blocking fs calls and swallow errors;
update load(), save(), and any helpers that call _getFilePath() to use
non-blocking fs.promises APIs (e.g., fs.promises.access/stat and
fs.promises.readFile/fs.promises.writeFile) with async/await, preserve and
surface error context by catching errors as (err) and including err.message/err
in any logs or error handling, and ensure you validate data.schemaVersion before
assigning this.reflections and this.patterns; specifically change the
synchronous fs.existsSync/readFileSync/writeFileSync usage in methods load() and
save() to async equivalents and adjust the try/catch blocks to accept an error
parameter and handle or rethrow it instead of silently resetting
reflections/patterns.
.aios-core/core/memory/reflection-engine.js (1)

111-115: ⚠️ Potential issue | 🟠 Major

_ensureLoaded() is defined but not enforced before read/write APIs.

Current public flows can still mutate/save state before hydration, risking overwrite of existing on-disk reflections. Wire _ensureLoaded() into public API entry points.

Also applies to: 155-183, 200-233

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/memory/reflection-engine.js around lines 111 - 115, Public
APIs can run before hydration; call await this._ensureLoaded() at the start of
every public read/write entry point (any methods that get, list, create, update,
save, delete reflections) so the in-memory state is loaded before any mutation
or read. Locate public methods in reflection-engine.js (the
save/create/update/delete/get/list-style functions referenced around lines
155-183 and 200-233) and add a top-of-method await this._ensureLoaded(); (ensure
it’s awaited, idempotent, and errors propagate) before any logic that reads or
mutates this._reflections or writes to disk.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.aios-core/core/memory/reflection-engine.js:
- Around line 156-157: The save() method currently returns early when
this._saving is true and sets this._saving = true then swallows errors (just
logs), which can drop concurrent save requests and hide persistence failures;
change save() (and the other similar block using this._saving) to track pending
saves instead of returning—e.g., set a pending flag or enqueue a request (use
names like _saving, _pendingSave or a simple _saveQueue) so concurrent callers
either await the current save or are queued to run after it, and ensure that on
error the promise rejects (rethrow or return the error) instead of only logging
so callers can detect failures and retry.
- Line 503: The forEach callback currently uses an expression-bodied arrow that
implicitly returns the result of Set#add (e.g., r.tags.forEach((t) =>
tagSet.add(t))); change these to block-bodied callbacks to avoid implicit
returns, e.g., r.tags.forEach((t) => { tagSet.add(t); }); and make the same
change for the other occurrence that uses forEach and tagSet.add (the similar
call around the second occurrence referenced in the review).

In @.aiox-core/core/memory/reflection-engine.js:
- Around line 161-164: The public API methods recordReflection,
getRecommendations, and injectContext currently assume their inputs are non-null
objects and can throw uncaught TypeError; add upfront shape guards in each
(e.g., for recordReflection verify typeof reflection === 'object' && reflection
!== null before accessing properties and throw a clear TypeError if invalid) and
do the same for getRecommendations and injectContext (check their respective
input parameter names), then keep the existing property checks
(taskType/agentId/outcome/strategy) after the object guard to preserve current
validation behavior.
- Line 449: The forEach callbacks currently use concise arrow bodies that
implicitly return values (e.g., r.tags.forEach((t) => tagSet.add(t))); update
these to use block bodies with an explicit void return by replacing the concise
arrow with a braced callback (e.g., r.tags.forEach((t) => { tagSet.add(t); }));
apply the same change to the other occurrence around the tag handling at the
later occurrence (the similar forEach at line ~502) so all forEach callbacks
that call tagSet.add or similar side-effecting functions use { ... } bodies to
satisfy the linter.

In @.aiox-core/data/entity-registry.yaml:
- Around line 12785-12800: The registry entry for "reflection-engine" is missing
the standard fields causing a schema outlier; update the node for
reflection-engine to include the standard registry fields externalDeps (empty
array if none), plannedDeps (empty array or planned entries), and lifecycle
(populate with the same lifecycle structure used by other module entries),
ensuring you modify the record that has path
".aiox-core/core/memory/reflection-engine.js" and preserve existing fields like
path, layer, type, purpose, keywords, usedBy, dependencies, adaptability,
checksum, and lastVerified.

In `@tests/core/memory/reflection-engine.test.js`:
- Around line 199-215: Add assertions that verify stale patterns and
recommendations are removed when reflections are evicted: after using
createEngine({ maxReflections: 3 }) and calling engine.recordReflection(...) to
trigger eviction (and when retention pruning is exercised in the other test
block), assert that engine.patterns no longer contains any pattern entries that
were produced only by the removed reflection(s) and that
engine.getRecommendations(...) does not return recommendations that relied on
those removed reflections; reuse existing symbols (engine, recordReflection,
engine.patterns, engine.getRecommendations, Events.REFLECTIONS_PRUNED,
pruneHandler) to locate the code and add these regression checks immediately
after the existing length/event assertions in both the max-limit test and the
retention-pruning test.
- Line 10: The test imports a non-canonical copy of ReflectionEngine which
diverges from the shipped module; update the import in
tests/core/memory/reflection-engine.test.js to require the canonical,
production-shipped ReflectionEngine module used by runtime (instead of
'../../../.aios-core/core/memory/reflection-engine') or make the two files
byte-identical so behavior matches; look for the ReflectionEngine export and
ensure the test references the same module that defines the ReflectionEngine
class/constructor and its members (e.g., _saving, save/restore logic) used in
production.

---

Outside diff comments:
In @.aiox-core/data/entity-registry.yaml:
- Around line 8732-8738: The registry entries currently list the same modules
(e.g., memory-query, gotchas-memory, session-memory) under both
shipped/externalDeps and plannedDeps; update the generator/regeneration logic to
ensure promoted/shipped dependencies are removed from plannedDeps. Concretely,
in the generator step that builds the plannedDeps list, subtract any dependency
present in externalDeps (or the shipped dependencies set) from plannedDeps so
they are mutually exclusive; ensure the code that produces the YAML for keys
plannedDeps and externalDeps reflects that difference and add a test that
regenerating the registry does not produce overlapping entries for names like
memory-query, gotchas-memory, session-memory.

---

Duplicate comments:
In @.aios-core/core/memory/reflection-engine.js:
- Around line 111-115: Public APIs can run before hydration; call await
this._ensureLoaded() at the start of every public read/write entry point (any
methods that get, list, create, update, save, delete reflections) so the
in-memory state is loaded before any mutation or read. Locate public methods in
reflection-engine.js (the save/create/update/delete/get/list-style functions
referenced around lines 155-183 and 200-233) and add a top-of-method await
this._ensureLoaded(); (ensure it’s awaited, idempotent, and errors propagate)
before any logic that reads or mutates this._reflections or writes to disk.

In @.aiox-core/core/memory/reflection-engine.js:
- Around line 100-120: The load() and save() methods currently use blocking fs
calls and swallow errors; update load(), save(), and any helpers that call
_getFilePath() to use non-blocking fs.promises APIs (e.g.,
fs.promises.access/stat and fs.promises.readFile/fs.promises.writeFile) with
async/await, preserve and surface error context by catching errors as (err) and
including err.message/err in any logs or error handling, and ensure you validate
data.schemaVersion before assigning this.reflections and this.patterns;
specifically change the synchronous fs.existsSync/readFileSync/writeFileSync
usage in methods load() and save() to async equivalents and adjust the try/catch
blocks to accept an error parameter and handle or rethrow it instead of silently
resetting reflections/patterns.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c887c74a-36bf-4690-8d25-c9cba842e661

📥 Commits

Reviewing files that changed from the base of the PR and between 9fedd30 and 1899e9d.

📒 Files selected for processing (8)
  • .aios-core/core/memory/reflection-engine.js
  • .aiox-core/core/execution/context-injector.js
  • .aiox-core/core/execution/subagent-dispatcher.js
  • .aiox-core/core/ideation/ideation-engine.js
  • .aiox-core/core/memory/reflection-engine.js
  • .aiox-core/data/entity-registry.yaml
  • .aiox-core/install-manifest.yaml
  • tests/core/memory/reflection-engine.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • .aiox-core/core/ideation/ideation-engine.js

Comment on lines +156 to +157
if (this._saving) return;
this._saving = true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

save() can lose updates and hide persistence failures.

Returning on _saving drops concurrent save requests, and the catch only logs. Track a pending-save flag (or queue) and rethrow/return failure so callers can retry.

Also applies to: 178-180

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/memory/reflection-engine.js around lines 156 - 157, The
save() method currently returns early when this._saving is true and sets
this._saving = true then swallows errors (just logs), which can drop concurrent
save requests and hide persistence failures; change save() (and the other
similar block using this._saving) to track pending saves instead of
returning—e.g., set a pending flag or enqueue a request (use names like _saving,
_pendingSave or a simple _saveQueue) so concurrent callers either await the
current save or are queued to run after it, and ensure that on error the promise
rejects (rethrow or return the error) instead of only logging so callers can
detect failures and retry.

// Collect all tags
const tagSet = new Set();
for (const r of all) {
if (Array.isArray(r.tags)) r.tags.forEach((t) => tagSet.add(t));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix implicit return inside forEach callbacks to satisfy lint rules.

Use block-bodied callbacks for r.tags.forEach(...) so the callback does not return Set#add implicitly.

Also applies to: 555-555

🧰 Tools
🪛 Biome (2.4.7)

[error] 503-503: This callback passed to forEach() iterable method should not return a value.

(lint/suspicious/useIterableCallbackReturn)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/memory/reflection-engine.js at line 503, The forEach
callback currently uses an expression-bodied arrow that implicitly returns the
result of Set#add (e.g., r.tags.forEach((t) => tagSet.add(t))); change these to
block-bodied callbacks to avoid implicit returns, e.g., r.tags.forEach((t) => {
tagSet.add(t); }); and make the same change for the other occurrence that uses
forEach and tagSet.add (the similar call around the second occurrence referenced
in the review).

Comment on lines +161 to +164
recordReflection(reflection) {
if (!reflection.taskType || !reflection.agentId || !reflection.outcome || !reflection.strategy) {
throw new Error('Required fields: taskType, agentId, outcome, strategy');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add object-shape guards on public API inputs.

recordReflection, getRecommendations, and injectContext assume object inputs and can throw uncaught TypeError on invalid caller input. Add upfront typeof x === 'object' && x !== null guards for stable API behavior.

As per coding guidelines "Check for proper input validation on public API methods."

Also applies to: 205-207, 290-294

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/core/memory/reflection-engine.js around lines 161 - 164, The
public API methods recordReflection, getRecommendations, and injectContext
currently assume their inputs are non-null objects and can throw uncaught
TypeError; add upfront shape guards in each (e.g., for recordReflection verify
typeof reflection === 'object' && reflection !== null before accessing
properties and throw a clear TypeError if invalid) and do the same for
getRecommendations and injectContext (check their respective input parameter
names), then keep the existing property checks
(taskType/agentId/outcome/strategy) after the object guard to preserve current
validation behavior.

// Collect all tags
const tagSet = new Set();
for (const r of all) {
if (r.tags) r.tags.forEach((t) => tagSet.add(t));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Adjust forEach callbacks to avoid lint error on implicit returns.

Biome flags these callbacks because tagSet.add(t) is returned implicitly. Wrap callback bodies in braces to return void.

Also applies to: 502-502

🧰 Tools
🪛 Biome (2.4.7)

[error] 449-449: This callback passed to forEach() iterable method should not return a value.

(lint/suspicious/useIterableCallbackReturn)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/core/memory/reflection-engine.js at line 449, The forEach
callbacks currently use concise arrow bodies that implicitly return values
(e.g., r.tags.forEach((t) => tagSet.add(t))); update these to use block bodies
with an explicit void return by replacing the concise arrow with a braced
callback (e.g., r.tags.forEach((t) => { tagSet.add(t); })); apply the same
change to the other occurrence around the tag handling at the later occurrence
(the similar forEach at line ~502) so all forEach callbacks that call tagSet.add
or similar side-effecting functions use { ... } bodies to satisfy the linter.

Comment on lines +12785 to +12800
reflection-engine:
path: .aiox-core/core/memory/reflection-engine.js
layer: L1
type: module
purpose: reflection.description || '',
keywords:
- reflection
- engine
usedBy: []
dependencies: []
adaptability:
score: 0.4
constraints: []
extensionPoints: []
checksum: sha256:e219009b9322b996ef0ab0ef25d70f8ddd26717d0e24e88129988fe63f8514ca
lastVerified: '2026-03-17T15:38:11.019Z'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Complete the new module node with the standard registry fields.

reflection-engine is missing externalDeps, plannedDeps, and lifecycle, unlike the surrounding modules entries. That makes this record a schema outlier for any consumer expecting the normal shape.

Suggested registry shape
    reflection-engine:
      path: .aiox-core/core/memory/reflection-engine.js
      layer: L1
      type: module
      purpose: reflection.description || '',
      keywords:
        - reflection
        - engine
      usedBy: []
      dependencies: []
+      externalDeps: []
+      plannedDeps: []
+      lifecycle: production
      adaptability:
        score: 0.4
        constraints: []
        extensionPoints: []
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aiox-core/data/entity-registry.yaml around lines 12785 - 12800, The
registry entry for "reflection-engine" is missing the standard fields causing a
schema outlier; update the node for reflection-engine to include the standard
registry fields externalDeps (empty array if none), plannedDeps (empty array or
planned entries), and lifecycle (populate with the same lifecycle structure used
by other module entries), ensuring you modify the record that has path
".aiox-core/core/memory/reflection-engine.js" and preserve existing fields like
path, layer, type, purpose, keywords, usedBy, dependencies, adaptability,
checksum, and lastVerified.


const fs = require('fs');
const path = require('path');
const ReflectionEngine = require('../../../.aios-core/core/memory/reflection-engine');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in .aios-core/core/memory/reflection-engine.js .aiox-core/core/memory/reflection-engine.js; do
  if [ -f "$f" ]; then
    printf 'FOUND %s\n' "$f"
    sha256sum "$f"
  else
    printf 'MISSING %s\n' "$f"
  fi
done

if [ -f .aios-core/core/memory/reflection-engine.js ] && [ -f .aiox-core/core/memory/reflection-engine.js ]; then
  printf '\n--- diff ---\n'
  diff -u .aios-core/core/memory/reflection-engine.js .aiox-core/core/memory/reflection-engine.js || true
fi

printf '\n--- current import ---\n'
sed -n '8,12p' tests/core/memory/reflection-engine.test.js

Repository: SynkraAI/aiox-core

Length of output: 178


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Checking for module files ==="
for f in .aios-core/core/memory/reflection-engine.js .aiox-core/core/memory/reflection-engine.js; do
  if [ -f "$f" ]; then
    echo "FOUND: $f"
    wc -l "$f"
  else
    echo "MISSING: $f"
  fi
done

echo ""
echo "=== Current import in test file ==="
sed -n '8,12p' tests/core/memory/reflection-engine.test.js

echo ""
echo "=== Checking if both files exist and comparing ==="
if [ -f .aios-core/core/memory/reflection-engine.js ] && [ -f .aiox-core/core/memory/reflection-engine.js ]; then
  echo "Both files found. Comparing..."
  diff -u .aios-core/core/memory/reflection-engine.js .aiox-core/core/memory/reflection-engine.js | head -50
else
  echo "One or both files missing - cannot compare"
fi

Repository: SynkraAI/aiox-core

Length of output: 2182


Update test import to use shipped module or ensure both copies remain identical.

The test imports from ../../../.aios-core/core/memory/reflection-engine (a relative path violating the absolute import guideline), but this differs significantly from the shipped .aiox-core/core/memory/reflection-engine.js. The files diverge in line count (592 vs 538 lines) with functional differences including removed docstrings, operator changes (??||), and missing properties like _saving. Tests can pass against the legacy .aios-core copy while the actual shipped module fails.

Either switch the import to the canonical module path consumed by production code, or ensure both copies remain byte-identical. Per coding guidelines, **/*.{js,jsx,ts,tsx} requires absolute imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/memory/reflection-engine.test.js` at line 10, The test imports a
non-canonical copy of ReflectionEngine which diverges from the shipped module;
update the import in tests/core/memory/reflection-engine.test.js to require the
canonical, production-shipped ReflectionEngine module used by runtime (instead
of '../../../.aios-core/core/memory/reflection-engine') or make the two files
byte-identical so behavior matches; look for the ReflectionEngine export and
ensure the test references the same module that defines the ReflectionEngine
class/constructor and its members (e.g., _saving, save/restore logic) used in
production.

Comment on lines +199 to +215
test('enforces maxReflections limit', () => {
const engine = createEngine({ maxReflections: 3 });
const pruneHandler = jest.fn();
engine.on(Events.REFLECTIONS_PRUNED, pruneHandler);

engine.recordReflection(makeReflection({ strategy: 's1' }));
engine.recordReflection(makeReflection({ strategy: 's2' }));
engine.recordReflection(makeReflection({ strategy: 's3' }));
expect(engine.reflections).toHaveLength(3);

engine.recordReflection(makeReflection({ strategy: 's4' }));
expect(engine.reflections).toHaveLength(3);
expect(engine.reflections[0].strategy).toBe('s2'); // s1 was removed
expect(pruneHandler).toHaveBeenCalledWith(
expect.objectContaining({ count: 1, reason: 'max_limit' }),
);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a regression assertion for stale pattern cleanup.

Lines 199-215 and 519-567 only verify count/event behavior. This PR also depends on patterns being recomputed after max-limit eviction and retention pruning, but the suite never proves that stale engine.patterns or getRecommendations() results disappear after supporting reflections are removed.

Also applies to: 519-567

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/memory/reflection-engine.test.js` around lines 199 - 215, Add
assertions that verify stale patterns and recommendations are removed when
reflections are evicted: after using createEngine({ maxReflections: 3 }) and
calling engine.recordReflection(...) to trigger eviction (and when retention
pruning is exercised in the other test block), assert that engine.patterns no
longer contains any pattern entries that were produced only by the removed
reflection(s) and that engine.getRecommendations(...) does not return
recommendations that relied on those removed reflections; reuse existing symbols
(engine, recordReflection, engine.patterns, engine.getRecommendations,
Events.REFLECTIONS_PRUNED, pruneHandler) to locate the code and add these
regression checks immediately after the existing length/event assertions in both
the max-limit test and the retention-pruning test.

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

Labels

area: agents Agent system related area: cli CLI tools (bin/, packages/aios-pro-cli/) area: core Core framework (.aios-core/core/) area: devops CI/CD, GitHub Actions (.github/) area: docs Documentation (docs/) area: health-check Health check system area: installer Installer and setup (packages/installer/) area: pro Pro features (pro/) area: synapse SYNAPSE context engine area: workflows Workflow system related mcp squad type: test Test coverage and quality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants