Skip to content

feat(learning): schedule consolidation maintenance#214

Merged
xinhuagu merged 4 commits into
mainfrom
codex/issue-127-consolidation-scheduler
Mar 13, 2026
Merged

feat(learning): schedule consolidation maintenance#214
xinhuagu merged 4 commits into
mainfrom
codex/issue-127-consolidation-scheduler

Conversation

@xinhuagu

@xinhuagu xinhuagu commented Mar 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • add a learning maintenance scheduler with time, session-count, size, and idle triggers
  • move heavy consolidation/mining/trend work out of per-session teardown and into the scheduler pipeline
  • expose backing-file size from auto-memory store and cover trigger behavior with dedicated tests

Testing

  • ./gradlew :aceclaw-daemon:test --tests dev.aceclaw.daemon.LearningMaintenanceSchedulerTest --tests dev.aceclaw.daemon.MemoryLifecycleIntegrationTest :aceclaw-memory:test --tests dev.aceclaw.memory.MemoryConsolidatorTest --tests dev.aceclaw.memory.TrendDetectorTest --tests dev.aceclaw.memory.CrossSessionPatternMinerTest --no-daemon

Closes #127

Summary by CodeRabbit

  • New Features

    • Deferred learning maintenance scheduler added — periodic and event-driven consolidation, cross-session pattern mining, and trend detection (configurable by time, session count, size, and idle).
  • Bug Fixes

    • Improved memory store thread-safety and added backing-file size reporting for maintenance decisions.
    • Scheduler lifecycle handled on startup/shutdown to ensure graceful runs and stops.
  • Tests

    • New tests for scheduler triggers and concurrent memory-store access (duplicate test entry present).
  • Documentation

    • Updated architecture and docs to describe deferred maintenance, learning pipeline, and revised learning lifecycle.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Introduce LearningMaintenanceScheduler with time, session-count, size, and idle triggers
• Move consolidation, mining, and trend detection from session teardown to scheduler pipeline
• Expose backing-file size from AutoMemoryStore via new largestBackingFileBytes() method
• Add comprehensive test coverage for scheduler trigger behavior
Diagram
flowchart LR
  A["Session Close Event"] -->|onSessionClosed| B["LearningMaintenanceScheduler"]
  C["Scheduler Tick"] -->|time/size/idle checks| B
  B -->|triggers maintenance| D["runLearningMaintenancePipeline"]
  D -->|consolidate| E["MemoryConsolidator"]
  D -->|mine patterns| F["CrossSessionPatternMiner"]
  D -->|detect trends| G["TrendDetector"]
  H["AutoMemoryStore"] -->|largestBackingFileBytes| B
Loading

Grey Divider

File Changes

1. aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java ✨ Enhancement +88/-41

Integrate learning maintenance scheduler into daemon lifecycle

• Add learningMaintenanceScheduler field and initialize it during daemon wiring
• Replace inline consolidation/mining/trend logic in session-end callback with scheduler trigger
• Implement runLearningMaintenancePipeline() method to execute maintenance tasks with trigger
 context
• Register scheduler for startup and shutdown lifecycle management

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java


2. aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java ✨ Enhancement +231/-0

New scheduler for periodic learning maintenance tasks

• New scheduler class with four trigger types: time-interval, session-count, size-threshold,
 idle-interval
• Implements concurrent execution using virtual threads and atomic state management
• Provides configurable thresholds with sensible defaults (6h time, 10 sessions, 50KB size, 5m idle)
• Supports arming/disarming of size and idle triggers to prevent repeated executions

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java


3. aceclaw-daemon/src/test/java/dev/aceclaw/daemon/LearningMaintenanceSchedulerTest.java 🧪 Tests +166/-0

Comprehensive test suite for scheduler triggers

• Test session-count trigger fires after threshold is reached
• Test time-interval trigger fires after configured duration
• Test size-threshold trigger fires once per crossing and re-arms on drop
• Test idle-interval trigger fires when no active sessions for configured duration
• Includes MutableClock utility for deterministic time-based testing

aceclaw-daemon/src/test/java/dev/aceclaw/daemon/LearningMaintenanceSchedulerTest.java


View more (1)
4. aceclaw-memory/src/main/java/dev/aceclaw/memory/AutoMemoryStore.java ✨ Enhancement +24/-0

Expose backing file size for scheduler triggers

• Add largestBackingFileBytes() method to expose backing JSONL file sizes
• Add private fileSize() helper to safely read file sizes with exception handling
• Method returns largest size among global and project-specific memory files

aceclaw-memory/src/main/java/dev/aceclaw/memory/AutoMemoryStore.java


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Mar 13, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (2)

Grey Divider


Action required

1. memory-consolidation CronJob missing 📎 Requirement gap ⛯ Reliability
Description
Daemon startup starts LearningMaintenanceScheduler but does not register a CronJob named
memory-consolidation with CronScheduler. As a result, the required 6-hour CronScheduler-based
schedule is not configured.
Code

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[R1607-1617]

+        if (learningMaintenanceScheduler != null) {
+            try {
+                learningMaintenanceScheduler.start();
+                shutdownManager.register(new ShutdownManager.ShutdownParticipant() {
+                    @Override public String name() { return "Learning Maintenance Scheduler"; }
+                    @Override public int priority() { return 87; }
+                    @Override public void onShutdown() { learningMaintenanceScheduler.stop(); }
+                });
+            } catch (Exception e) {
+                log.error("Learning maintenance scheduler startup failed (daemon will continue): {}", e.getMessage(), e);
+            }
Evidence
Compliance requires a CronScheduler-registered job named memory-consolidation that runs every 6
hours. The new startup code only starts LearningMaintenanceScheduler, and the scheduler uses its
own ScheduledExecutorService instead of registering a CronScheduler job.

Register memory consolidation cron job during daemon startup
Time-based trigger runs consolidation every 6 hours
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[1607-1617]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java[64-71]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Daemon startup does not register a `CronJob` named `memory-consolidation` with `CronScheduler`, and the 6-hour schedule is implemented via an internal `ScheduledExecutorService` instead of `CronScheduler`.
## Issue Context
Compliance requires the daemon to automatically register a `CronScheduler` job named `memory-consolidation` during startup, and that job must execute every 6 hours.
## Fix Focus Areas
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[1607-1618]
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java[59-77]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. runLearningMaintenancePipeline missing stages 📎 Requirement gap ✓ Correctness
Description
The new maintenance pipeline runs consolidation, cross-session mining, and trend detection only,
omitting SessionAnalyzer, stale HistoricalLogIndex rebuild, and feeding results into AutoMemoryStore
+ SkillProposalEngine. This breaks the required end-to-end retrospective learning sequence.
Code

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[R1774-1828]

+    private void runLearningMaintenancePipeline(
+            String trigger,
+            AutoMemoryStore memoryStore,
+            Path archiveDir,
+            DailyJournal journal,
+            CrossSessionPatternMiner crossSessionPatternMiner,
+            TrendDetector trendDetector,
+            String workspaceHash,
+            Path workingDir
+    ) {
+        try {
+            var result = MemoryConsolidator.consolidate(memoryStore, workingDir, archiveDir);
+            if (result.hasChanges() && journal != null) {
+                journal.append("Memory consolidated (" + trigger + "): "
+                        + result.deduped() + " deduped, "
+                        + result.merged() + " merged, "
+                        + result.pruned() + " pruned");
+            }
+        } catch (Exception e) {
+            log.warn("Memory consolidation failed (trigger={}): {}", trigger, e.getMessage());
+        }
+
+        if (crossSessionPatternMiner != null && historicalLogIndex != null) {
+            try {
+                var mined = crossSessionPatternMiner.mine(
+                        historicalLogIndex, memoryStore, workspaceHash, workingDir);
+                if ((!mined.frequentErrorChains().isEmpty()
+                        || !mined.stableWorkflows().isEmpty()
+                        || !mined.convergingStrategies().isEmpty()
+                        || !mined.degradationSignals().isEmpty())
+                        && journal != null) {
+                    journal.append("Cross-session miner (" + trigger + "): "
+                            + mined.frequentErrorChains().size() + " error chains, "
+                            + mined.stableWorkflows().size() + " stable workflows, "
+                            + mined.convergingStrategies().size() + " converging strategies, "
+                            + mined.degradationSignals().size() + " degradation signals");
+                }
+            } catch (Exception e) {
+                log.warn("Cross-session pattern mining failed (trigger={}): {}", trigger, e.getMessage());
+            }
+        }
+
+        if (trendDetector != null && historicalLogIndex != null) {
+            try {
+                var trends = trendDetector.detect(
+                        historicalLogIndex, memoryStore, workspaceHash, workingDir);
+                if (!trends.isEmpty() && journal != null) {
+                    journal.append("Trend detector (" + trigger + "): " + trends.size()
+                            + " significant trends. " + summarizeTrends(trends));
+                }
+            } catch (Exception e) {
+                log.warn("Trend detection failed (trigger={}): {}", trigger, e.getMessage());
+            }
+        }
+    }
Evidence
Compliance ID 6 mandates a single triggered run execute six ordered stages; the added
runLearningMaintenancePipeline only implements stages (1), (4), and (5) and does not show index
rebuild, session analysis, or feeding results to AutoMemoryStore/SkillProposalEngine.

Execute full retrospective learning pipeline in the specified order
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[1774-1828]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`runLearningMaintenancePipeline` omits required stages from the retrospective learning pipeline (SessionAnalyzer, conditional HistoricalLogIndex rebuild, and feeding outputs to AutoMemoryStore + SkillProposalEngine).
## Issue Context
Compliance requires a single triggered run to execute the full ordered pipeline: (1) MemoryConsolidator, (2) SessionAnalyzer, (3) HistoricalLogIndex rebuild (if stale), (4) CrossSessionPatternMiner, (5) TrendDetector, (6) feed results to AutoMemoryStore + SkillProposalEngine.
## Fix Focus Areas
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[1774-1828]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Triggers run while stopped🐞 Bug ⛯ Reliability
Description
LearningMaintenanceScheduler.onSessionClosed() can start maintenance even before start() or after
stop() because it calls tryTrigger() without checking running state. This can run
consolidation/mining during daemon wiring or shutdown when the scheduler is supposed to be inactive.
Code

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java[R101-104]

+    public void onSessionClosed() {
+        sessionsSinceLastRun.incrementAndGet();
+        tryTrigger(Trigger.SESSION_COUNT);
+    }
Evidence
stop() flips running=false, but onSessionClosed() still calls tryTrigger(), and tryTrigger() spawns
a virtual thread unconditionally when conditions match. AceClawDaemon wires onSessionClosed() into
the session-end callback during wiring, while scheduler.start() is invoked later in start(),
creating a window where session closures can trigger maintenance before the scheduler is started
(and similarly after stop during shutdown).

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java[79-104]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java[125-150]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[620-701]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[1607-1614]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`LearningMaintenanceScheduler.onSessionClosed()` can trigger maintenance even when the scheduler has not been started yet or has already been stopped.
### Issue Context
`tick()` correctly checks `running`, but `onSessionClosed()` does not. Because the daemon wires the session-end callback before calling `learningMaintenanceScheduler.start()`, session closures can start maintenance runs during wiring/shutdown.
### Fix Focus Areas
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java[101-104]
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java[125-151]
### Suggested change
- Add `if (!running.get()) { return; }` at the start of `onSessionClosed()`.
- (Optional but safer) Add an early `if (!running.get()) return;` in `tryTrigger()` as a second line of defense.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Consolidation races access tracking🐞 Bug ⛯ Reliability
Description
Async maintenance runs MemoryConsolidator.consolidate(), which calls
AutoMemoryStore.replaceEntries() and clears/rebuilds the entries list while other threads call
AutoMemoryStore.query()/search() and update access counts via trackAccess(). Because
replaceEntries() only uses fileLock and trackAccess() iterates by index without coordinating with
fileLock, concurrent execution can throw IndexOutOfBoundsException and break normal request
processing.
Code

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java[R125-150]

+    private void tryTrigger(Trigger trigger) {
+        if (!shouldTrigger(trigger)) {
+            return;
+        }
+        if (!maintenanceRunning.compareAndSet(false, true)) {
+            return;
+        }
+
+        Thread.ofVirtual().name("learning-maintenance-" + trigger.id()).start(() -> {
+            try {
+                pipeline.run(trigger.id());
+                lastRunAt = clock.instant();
+                sessionsSinceLastRun.set(0);
+                if (trigger == Trigger.SIZE_THRESHOLD) {
+                    sizeTriggerArmed = false;
+                }
+                if (trigger == Trigger.IDLE_INTERVAL) {
+                    idleTriggerArmed = false;
+                }
+                log.info("Learning maintenance completed via trigger={}", trigger.id());
+            } catch (Exception e) {
+                log.warn("Learning maintenance failed via trigger={}: {}", trigger.id(), e.getMessage());
+            } finally {
+                maintenanceRunning.set(false);
+            }
+        });
Evidence
LearningMaintenanceScheduler runs the maintenance pipeline on a virtual thread, and AceClawDaemon’s
pipeline includes MemoryConsolidator.consolidate(), which rewrites the store via
AutoMemoryStore.replaceEntries(). Meanwhile, normal request-time components call
memoryStore.query(), which calls trackAccess() and iterates entries by index (entries.size() +
entries.get(i)), while replaceEntries() can concurrently entries.clear(); this interleaving can
produce IndexOutOfBoundsException.

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java[125-150]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[1774-1794]
aceclaw-memory/src/main/java/dev/aceclaw/memory/MemoryConsolidator.java[60-85]
aceclaw-memory/src/main/java/dev/aceclaw/memory/AutoMemoryStore.java[267-285]
aceclaw-memory/src/main/java/dev/aceclaw/memory/AutoMemoryStore.java[226-247]
aceclaw-memory/src/main/java/dev/aceclaw/memory/AutoMemoryStore.java[506-523]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/PatternDetector.java[106-120]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`AutoMemoryStore.replaceEntries()` clears and rebuilds the `entries` list under `fileLock`, while `query()/search()` call `trackAccess()` which iterates `entries` by index under `accessLock` only. With consolidation now running asynchronously, these paths can overlap and cause `IndexOutOfBoundsException` in `trackAccess()`.
### Issue Context
The PR moves consolidation into `LearningMaintenanceScheduler` which runs the pipeline on a virtual thread, increasing the likelihood of concurrent `replaceEntries()` with normal request-time `query()/search()`.
### Fix Focus Areas
- aceclaw-memory/src/main/java/dev/aceclaw/memory/AutoMemoryStore.java[267-285]
- aceclaw-memory/src/main/java/dev/aceclaw/memory/AutoMemoryStore.java[506-523]
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java[125-150]
### Suggested change
Implement a consistent locking strategy so `replaceEntries()` cannot interleave with `trackAccess()`:
- Option A (minimal change): In `replaceEntries()`, acquire `accessLock` in addition to `fileLock` during `entries.clear()` / `entries.addAll(...)` and related mutations. Ensure a consistent lock acquisition order across the class (e.g., always `accessLock` then `fileLock` for operations needing both).
- Option B (cleaner): Replace `fileLock`/`accessLock` split with a single `ReentrantReadWriteLock` for `entries` mutations vs reads, and ensure `trackAccess()` uses the appropriate lock.
Add a regression test that runs `query()` concurrently with a consolidation-triggered `replaceEntries()` and asserts no exceptions occur.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@coderabbitai

coderabbitai Bot commented Mar 13, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@xinhuagu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 59 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 78c478b5-43fd-47e3-90cb-b2b4dbccfbb4

📥 Commits

Reviewing files that changed from the base of the PR and between 3702363 and 09d4716.

📒 Files selected for processing (3)
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java
  • aceclaw-daemon/src/test/java/dev/aceclaw/daemon/LearningMaintenanceSchedulerTest.java
  • aceclaw-memory/src/main/java/dev/aceclaw/memory/AutoMemoryStore.java
📝 Walkthrough

Walkthrough

Adds a configurable LearningMaintenanceScheduler and integrates it into AceClawDaemon to centralize and schedule deferred memory consolidation, cross-session pattern mining, and trend detection; AutoMemoryStore can report largest backing file size; scheduler unit tests and documentation updates included.

Changes

Cohort / File(s) Summary
Learning Maintenance Scheduler
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java
New scheduler class with Config, Trigger enum, and MaintenancePipeline. Implements tick loop, time/session/size/idle triggers, lifecycle (start/stop), concurrency guards, and executes pipeline on isolated threads.
Daemon Integration
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java
Added learningMaintenanceScheduler field; start/stop lifecycle wiring; replaced inline per-session consolidation/mining with learningMaintenanceScheduler.onSessionClosed(); added runLearningMaintenancePipeline(...) to centralize consolidation, cross-session mining, trend detection, and journaling.
Scheduler Tests
aceclaw-daemon/src/test/java/dev/aceclaw/daemon/LearningMaintenanceSchedulerTest.java
New test suite using a MutableClock and suppliers to validate time, session-count, size, and idle triggers, trigger ordering, and stop behavior.
Memory Store
aceclaw-memory/src/main/java/dev/aceclaw/memory/AutoMemoryStore.java
Added public long largestBackingFileBytes(Path projectPath) and private fileSize(Path) helper; introduced accessLock usage around key add/replace/remove operations to improve thread-safety.
Memory Store Tests
aceclaw-memory/src/test/java/dev/aceclaw/memory/AutoMemoryStoreTest.java
Added a concurrency test concurrentQueryAndReplaceEntriesDoNotThrow to exercise concurrent query and replaceEntries; note: the test appears duplicated in the file.
Docs & Product README
PRD.md, README.md, docs/memory-system-design.md, docs/self-learning.md
Documentation updates to describe deferred learning-maintenance scheduler, revised architecture and flows (session retrospectives + deferred consolidation/mining/trend detection), version bump and roadmap/context updates.
Other Small surface-area edits (imports, lifecycle handling) across daemon and tests to support new scheduler and pipeline integration.

Sequence Diagram

sequenceDiagram
    participant Daemon as AceClawDaemon
    participant Scheduler as LearningMaintenanceScheduler
    participant Executor as ScheduledExecutor
    participant Pipeline as MaintenancePipeline
    participant Memory as AutoMemoryStore
    participant Miners as CrossSession/TrendDetector

    Daemon->>Scheduler: start()
    Scheduler->>Executor: start tick loop

    Executor->>Scheduler: tick()
    Scheduler->>Scheduler: evaluate triggers (TIME, SIZE, IDLE)
    alt trigger condition met
        Scheduler->>Pipeline: run(trigger)  -- virtual thread
        Pipeline->>Memory: consolidateMemory()
        Pipeline->>Miners: mineCrossSessionPatterns()
        Pipeline->>Miners: detectTrends()
        Pipeline->>Memory: writeDailyJournal()
    end

    Daemon->>Scheduler: onSessionClosed()
    Scheduler->>Scheduler: increment session count
    alt sessionCount ≥ threshold
        Scheduler->>Pipeline: run("session-count")
        Pipeline->>Memory: consolidateMemory()
        Pipeline->>Miners: mine + detect
    end

    Daemon->>Scheduler: stop()
    Scheduler->>Executor: shutdown
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(learning): schedule consolidation maintenance' clearly and concisely summarizes the main change: introducing a scheduler for consolidation maintenance tasks.
Linked Issues check ✅ Passed The PR implements all primary coding requirements from issue #127: LearningMaintenanceScheduler with four triggers (time, session-count, size, idle), pipeline execution order, virtual thread execution, and backing-file size exposure.
Out of Scope Changes check ✅ Passed All changes directly support the consolidation scheduler feature: daemon integration, scheduler implementation, thread-safety enhancements, comprehensive tests, and aligned documentation updates.
Block Major Correctness And Security Risks ✅ Passed PR demonstrates robust concurrency control through proper AtomicBoolean/AtomicInteger guards, synchronized lifecycle locking, and ReentrantLock with finally blocks. Virtual thread execution includes try-catch-finally protection. File operations use atomic write-temp-rename patterns.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/issue-127-consolidation-scheduler
📝 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.

Comment on lines +1607 to +1617
if (learningMaintenanceScheduler != null) {
try {
learningMaintenanceScheduler.start();
shutdownManager.register(new ShutdownManager.ShutdownParticipant() {
@Override public String name() { return "Learning Maintenance Scheduler"; }
@Override public int priority() { return 87; }
@Override public void onShutdown() { learningMaintenanceScheduler.stop(); }
});
} catch (Exception e) {
log.error("Learning maintenance scheduler startup failed (daemon will continue): {}", e.getMessage(), e);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. memory-consolidation cronjob missing 📎 Requirement gap ⛯ Reliability

Daemon startup starts LearningMaintenanceScheduler but does not register a CronJob named
memory-consolidation with CronScheduler. As a result, the required 6-hour CronScheduler-based
schedule is not configured.
Agent Prompt
## Issue description
Daemon startup does not register a `CronJob` named `memory-consolidation` with `CronScheduler`, and the 6-hour schedule is implemented via an internal `ScheduledExecutorService` instead of `CronScheduler`.

## Issue Context
Compliance requires the daemon to automatically register a `CronScheduler` job named `memory-consolidation` during startup, and that job must execute every 6 hours.

## Fix Focus Areas
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[1607-1618]
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java[59-77]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +1774 to +1828
private void runLearningMaintenancePipeline(
String trigger,
AutoMemoryStore memoryStore,
Path archiveDir,
DailyJournal journal,
CrossSessionPatternMiner crossSessionPatternMiner,
TrendDetector trendDetector,
String workspaceHash,
Path workingDir
) {
try {
var result = MemoryConsolidator.consolidate(memoryStore, workingDir, archiveDir);
if (result.hasChanges() && journal != null) {
journal.append("Memory consolidated (" + trigger + "): "
+ result.deduped() + " deduped, "
+ result.merged() + " merged, "
+ result.pruned() + " pruned");
}
} catch (Exception e) {
log.warn("Memory consolidation failed (trigger={}): {}", trigger, e.getMessage());
}

if (crossSessionPatternMiner != null && historicalLogIndex != null) {
try {
var mined = crossSessionPatternMiner.mine(
historicalLogIndex, memoryStore, workspaceHash, workingDir);
if ((!mined.frequentErrorChains().isEmpty()
|| !mined.stableWorkflows().isEmpty()
|| !mined.convergingStrategies().isEmpty()
|| !mined.degradationSignals().isEmpty())
&& journal != null) {
journal.append("Cross-session miner (" + trigger + "): "
+ mined.frequentErrorChains().size() + " error chains, "
+ mined.stableWorkflows().size() + " stable workflows, "
+ mined.convergingStrategies().size() + " converging strategies, "
+ mined.degradationSignals().size() + " degradation signals");
}
} catch (Exception e) {
log.warn("Cross-session pattern mining failed (trigger={}): {}", trigger, e.getMessage());
}
}

if (trendDetector != null && historicalLogIndex != null) {
try {
var trends = trendDetector.detect(
historicalLogIndex, memoryStore, workspaceHash, workingDir);
if (!trends.isEmpty() && journal != null) {
journal.append("Trend detector (" + trigger + "): " + trends.size()
+ " significant trends. " + summarizeTrends(trends));
}
} catch (Exception e) {
log.warn("Trend detection failed (trigger={}): {}", trigger, e.getMessage());
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. runlearningmaintenancepipeline missing stages 📎 Requirement gap ✓ Correctness

The new maintenance pipeline runs consolidation, cross-session mining, and trend detection only,
omitting SessionAnalyzer, stale HistoricalLogIndex rebuild, and feeding results into AutoMemoryStore
+ SkillProposalEngine. This breaks the required end-to-end retrospective learning sequence.
Agent Prompt
## Issue description
`runLearningMaintenancePipeline` omits required stages from the retrospective learning pipeline (SessionAnalyzer, conditional HistoricalLogIndex rebuild, and feeding outputs to AutoMemoryStore + SkillProposalEngine).

## Issue Context
Compliance requires a single triggered run to execute the full ordered pipeline: (1) MemoryConsolidator, (2) SessionAnalyzer, (3) HistoricalLogIndex rebuild (if stale), (4) CrossSessionPatternMiner, (5) TrendDetector, (6) feed results to AutoMemoryStore + SkillProposalEngine.

## Fix Focus Areas
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[1774-1828]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java (1)

1607-1618: Consider distinct shutdown priorities for schedulers.

Both LearningMaintenanceScheduler and DeferredActionScheduler (line 1628) use shutdown priority 87. While they don't appear to share critical resources, using distinct priorities (e.g., 87 and 86) would make shutdown ordering deterministic and easier to reason about.

Suggested fix
                 shutdownManager.register(new ShutdownManager.ShutdownParticipant() {
                     `@Override` public String name() { return "Learning Maintenance Scheduler"; }
-                    `@Override` public int priority() { return 87; }
+                    `@Override` public int priority() { return 86; }
                     `@Override` public void onShutdown() { learningMaintenanceScheduler.stop(); }
                 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java` around
lines 1607 - 1618, The two scheduler shutdown participants both return the same
priority (87) which makes shutdown ordering ambiguous; update one of them
(either the ShutdownManager.ShutdownParticipant registered for
learningMaintenanceScheduler or the one for DeferredActionScheduler) to a
distinct integer (e.g., keep learningMaintenanceScheduler.priority() == 87 and
change DeferredActionScheduler.priority() to 86, or vice versa) so the shutdown
order is deterministic; locate the anonymous ShutdownManager.ShutdownParticipant
implementations registered around the learningMaintenanceScheduler and
DeferredActionScheduler startup code and change the priority() return value
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java`:
- Around line 1607-1618: The two scheduler shutdown participants both return the
same priority (87) which makes shutdown ordering ambiguous; update one of them
(either the ShutdownManager.ShutdownParticipant registered for
learningMaintenanceScheduler or the one for DeferredActionScheduler) to a
distinct integer (e.g., keep learningMaintenanceScheduler.priority() == 87 and
change DeferredActionScheduler.priority() to 86, or vice versa) so the shutdown
order is deterministic; locate the anonymous ShutdownManager.ShutdownParticipant
implementations registered around the learningMaintenanceScheduler and
DeferredActionScheduler startup code and change the priority() return value
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7d448f68-54b0-479c-9d12-69f4bf7666a6

📥 Commits

Reviewing files that changed from the base of the PR and between 4dcb33b and 0c5028b.

📒 Files selected for processing (4)
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java
  • aceclaw-daemon/src/test/java/dev/aceclaw/daemon/LearningMaintenanceSchedulerTest.java
  • aceclaw-memory/src/main/java/dev/aceclaw/memory/AutoMemoryStore.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java`:
- Around line 59-70: The start() method sets running=true before
creating/assigning scheduler, allowing stop() to see scheduler==null and miss
shutting down a later-created executor; fix by creating and scheduling the
executor into a local variable first (use the same scheduler creation block and
scheduleAtFixedRate with tickMillis), then attempt to set running via
running.compareAndSet(false, true); if the CAS fails (stop raced in),
immediately shutdownNow/terminate the newly-created executor to avoid leaking
threads and return; update references to the instance field scheduler only after
successfully setting running, and mirror the same serialized lifecycle logic in
stop() to read/clear scheduler safely.
- Around line 139-144: Before calling pipeline.run(trigger.id()) capture the
current sessionsSinceLastRun (e.g., startSessions = sessionsSinceLastRun.get()),
then after pipeline.run(...) compute delta = sessionsSinceLastRun.get() -
startSessions and set sessionsSinceLastRun to delta instead of unconditionally
setting it to 0; this preserves any session-close increments that happened while
the virtual thread was running. Update the code around
Thread.ofVirtual(...){...} where pipeline.run(...), lastRunAt and
sessionsSinceLastRun are handled (symbols: Thread.ofVirtual, pipeline.run,
lastRunAt, sessionsSinceLastRun, Trigger.SIZE_THRESHOLD) and use AtomicInteger
atomic methods (get, set/getAndSet) or a CAS loop to ensure thread-safe update
of the preserved delta.

In `@aceclaw-memory/src/main/java/dev/aceclaw/memory/AutoMemoryStore.java`:
- Around line 347-352: The fileSize(Path file) method in AutoMemoryStore is
silently swallowing IOExceptions and returning 0L; change it to catch
IOException, log a low-noise message including the file path and the exception
(e.g., LOGGER.debug/trace or LOGGER.warn with low frequency) so permission/disk
errors are visible, then return 0L as before; ensure you reference the existing
logger instance in AutoMemoryStore (or add one if missing) and include the
exception object in the log call for full stack/causal info.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8340c4fa-1c55-4da2-87c3-8970aacfdc72

📥 Commits

Reviewing files that changed from the base of the PR and between 0c5028b and 4d95839.

📒 Files selected for processing (4)
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java
  • aceclaw-daemon/src/test/java/dev/aceclaw/daemon/LearningMaintenanceSchedulerTest.java
  • aceclaw-memory/src/main/java/dev/aceclaw/memory/AutoMemoryStore.java
  • aceclaw-memory/src/test/java/dev/aceclaw/memory/AutoMemoryStoreTest.java

Comment thread aceclaw-daemon/src/main/java/dev/aceclaw/daemon/LearningMaintenanceScheduler.java Outdated
Comment on lines +347 to 352
private static long fileSize(Path file) {
try {
return Files.exists(file) ? Files.size(file) : 0L;
} catch (IOException e) {
return 0L;
}

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

Avoid silently swallowing file-size read failures

At Line 350, returning 0L on IOException without logging hides permission/disk issues and can silently disable size-threshold maintenance behavior.

💡 Suggested fix (add low-noise logging)
     private static long fileSize(Path file) {
         try {
             return Files.exists(file) ? Files.size(file) : 0L;
         } catch (IOException e) {
+            log.warn("Failed to read backing file size for {}: {}", file, e.getMessage());
             return 0L;
         }
     }
📝 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
private static long fileSize(Path file) {
try {
return Files.exists(file) ? Files.size(file) : 0L;
} catch (IOException e) {
return 0L;
}
private static long fileSize(Path file) {
try {
return Files.exists(file) ? Files.size(file) : 0L;
} catch (IOException e) {
log.warn("Failed to read backing file size for {}: {}", file, e.getMessage());
return 0L;
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@aceclaw-memory/src/main/java/dev/aceclaw/memory/AutoMemoryStore.java` around
lines 347 - 352, The fileSize(Path file) method in AutoMemoryStore is silently
swallowing IOExceptions and returning 0L; change it to catch IOException, log a
low-noise message including the file path and the exception (e.g.,
LOGGER.debug/trace or LOGGER.warn with low frequency) so permission/disk errors
are visible, then return 0L as before; ensure you reference the existing logger
instance in AutoMemoryStore (or add one if missing) and include the exception
object in the log call for full stack/causal info.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
docs/memory-system-design.md (1)

3-3: Consider updating the document version.

This document retains version "2.1 | 2026-02-18" while docs/self-learning.md has been updated to "2.0 | 2026-03-13" (newer date, lower version number). Since both documents have undergone substantial updates to reflect the deferred maintenance architecture, consider synchronizing version numbers for consistency.

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

In `@docs/memory-system-design.md` at line 3, The document header contains
"Version 2.1 | 2026-02-18" which is inconsistent with docs/self-learning.md
("Version 2.0 | 2026-03-13"); update the version header line (the string
"Version 2.1 | 2026-02-18") in memory-system-design.md to a synchronized value
(for example "Version 2.2 | 2026-03-13") and ensure any internal metadata or
front-matter that references the version/date is updated to the same new
version/date so both docs reflect a consistent release identifier.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/memory-system-design.md`:
- Line 3: The document header contains "Version 2.1 | 2026-02-18" which is
inconsistent with docs/self-learning.md ("Version 2.0 | 2026-03-13"); update the
version header line (the string "Version 2.1 | 2026-02-18") in
memory-system-design.md to a synchronized value (for example "Version 2.2 |
2026-03-13") and ensure any internal metadata or front-matter that references
the version/date is updated to the same new version/date so both docs reflect a
consistent release identifier.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a802527f-1405-4ec1-9717-d767dea4dd3d

📥 Commits

Reviewing files that changed from the base of the PR and between 4d95839 and 3702363.

📒 Files selected for processing (4)
  • PRD.md
  • README.md
  • docs/memory-system-design.md
  • docs/self-learning.md

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(learning): Consolidation Scheduler — auto-trigger memory maintenance

1 participant