Skip to content

Commit 73c5f5c

Browse files
committed
Improve memory quality: utility-based injection reranking and smarter dreamer archival
- Rerank memory injection by utility tier (retrieved > constraint-keyword > other) instead of seen_count alone, with shorter-content tiebreak to fit more in budget - Overhaul dreamer archive-stale prompt with structured archive/keep criteria, category-specific protection (CONSTRAINTS nearly untouchable, KNOWN_ISSUES never archived), and 'because'/'to prevent' keyword preservation for ARCHITECTURE_DECISIONS - Pass active user memories into archive-stale as Global User Profile context so dreamer can identify project memories redundant with globally-injected user profile
1 parent 09b019f commit 73c5f5c

3 files changed

Lines changed: 101 additions & 35 deletions

File tree

packages/plugin/src/features/magic-context/dreamer/runner.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import { getMemoryCountsByStatus } from "../memory/storage-memory";
2121
import { getPendingSmartNotes, markNoteChecked, markNoteReady } from "../storage-notes";
2222
import { reviewUserMemories } from "../user-memory/review-user-memories";
23+
import { getActiveUserMemories } from "../user-memory/storage-user-memory";
2324
import { acquireLease, getLeaseHolder, releaseLease, renewLease } from "./lease";
2425
import {
2526
clearStaleEntries,
@@ -503,10 +504,20 @@ export async function runDream(args: {
503504
}
504505
: undefined;
505506

507+
// Load user memories for archive-stale dedup context
508+
const userMemories =
509+
taskName === "archive-stale"
510+
? getActiveUserMemories(args.db).map((um) => ({
511+
id: um.id,
512+
content: um.content,
513+
}))
514+
: undefined;
515+
506516
const taskPrompt = buildDreamTaskPrompt(taskName, {
507517
projectPath: args.projectIdentity,
508518
lastDreamAt,
509519
existingDocs,
520+
userMemories,
510521
});
511522

512523
const createResponse = await args.client.session.create({

packages/plugin/src/features/magic-context/dreamer/task-prompts.ts

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -106,58 +106,81 @@ Check verifiable memories against actual repository state. Update stale wording,
106106

107107
// ── Archive Stale ──────────────────────────────────────────────────────────
108108

109-
export function buildArchiveStalePrompt(projectPath: string): string {
109+
export function buildArchiveStalePrompt(
110+
projectPath: string,
111+
userMemories?: Array<{ id: number; content: string }>,
112+
): string {
113+
const userProfileBlock =
114+
userMemories && userMemories.length > 0
115+
? `
116+
### Global User Profile (already injected into ALL sessions across ALL projects)
117+
These user memories are ALREADY available to the agent globally. Project memories that merely restate the same preference/rule are redundant and should be archived — but ONLY if the project memory adds ZERO project-specific detail beyond what the global memory already says.
118+
119+
${userMemories.map((um) => `- [U${um.id}] ${um.content}`).join("\n")}
120+
`
121+
: "";
122+
110123
return `## Task: Archive Stale Memories
111124
112125
**Project:** ${projectPath}
113126
114127
### Goal
115-
Find and archive memories that reference removed features, discontinued tools, old paths, obsolete workflows, or completed one-time instructions.
128+
Find and archive memories that waste the limited injection budget (~6000 tokens, fits ~150 memories).
129+
${userProfileBlock}
130+
### Archive criteria (archive IF any apply)
131+
132+
1. **Code restatement without rationale** — merely describes what code does without explaining WHY or what would break if changed.
133+
- Archive: "Tag assignment uses one DB transaction" (obvious from code)
134+
- Keep: "Tag assignment uses one DB transaction because tags rows and session_meta.counter must stay in sync" (explains the constraint)
135+
136+
2. **Redundant with other memories** — same information expressed differently. Keep the better-worded one.
137+
138+
3. **Stale implementation detail** — references specific functions, line numbers, or internal structures that change frequently and are better found by reading code.
139+
- Archive: "Function X is called at line 289 of file Y"
140+
- Keep: "Feature X requires Y to be initialized before Z" (design constraint)
141+
142+
4. **Low retrieval signal** — seen_count=1, retrieval_count=0, and no constraint language. These were promoted once but never needed again.
143+
144+
5. **Redundant with global user profile** — ONLY if the project memory adds ZERO project-specific detail beyond what the global memory already says. A project memory that applies a global principle to a specific context (e.g., "cache awareness is highest priority" applies a general principle to THIS project's north star) is NOT redundant — it narrows the global principle.
145+
146+
6. **Bare config defaults** — single-line values like \`enabled=true\` or \`experimental.X=false\` with no surrounding explanation or rationale.
147+
148+
7. **Completed one-time instructions** — imperative USER_DIRECTIVES like "Add X", "Create Y", "Publish as Z" where the action has clearly been done.
149+
150+
### Keep criteria (keep IF ANY apply — these OVERRIDE archive criteria)
151+
152+
1. **Contains constraint/rule** — uses "must", "never", "always", "cannot", "should not". CONSTRAINTS category gets extra protection: only archive if the EXACT same constraint exists word-for-word in another memory.
153+
2. **Captures non-obvious design reasoning** — explains WHY, not just WHAT. Look for "because", "so that", "to prevent", "to avoid".
154+
3. **Project-specific behavioral rule** — even if it sounds generic, if it's in USER_DIRECTIVES it was explicitly stated by the user for this project. Only archive if: (a) the action is clearly completed, or (b) it is 100% identical in scope to a global user memory.
155+
4. **Post-failure learning** — memories that encode lessons learned from real bugs, regressions, or user corrections. These prevent re-encountering the same problem.
156+
5. **Environment/path information** — saves agent from hunting for locations.
157+
6. **Config defaults with context** — prevents wrong assumptions. Archive ONLY bare values with no surrounding explanation.
158+
7. **Known issues** — prevents re-encountering solved problems. NEVER archive KNOWN_ISSUES.
159+
8. **High retrieval signal** — retrieval_count > 0 means the agent actually searched for this.
160+
9. **Priority/philosophy statements** — "X is the highest priority" or "north star" type directives that shape all decisions.
116161
117162
### Process
118163
119164
1. **List all active memories** with \`ctx_memory(action="list")\`.
120-
2. **Scan for staleness signals:**
121-
- References to tools that no longer exist (grep the tool registry)
122-
- References to files or directories that were deleted or renamed
123-
- References to old repository names, branches, or workflows
124-
- References to features explicitly described as "removed" or "replaced"
125-
- References to config keys that no longer appear in the schema
126-
- Session-local context that has no ongoing value ("in this session", "earlier today")
127-
- **Completed one-time instructions** in USER_DIRECTIVES — imperative directives like "Add X", "Create Y", "License as MIT", "Publish as Z" where the action has clearly been done (check the codebase to confirm completion)
128-
- **Low-value implementation minutiae** in ARCHITECTURE_DECISIONS — single-line statements that merely restate what code does without explaining WHY or capturing a non-obvious constraint. Example: "Tag assignment uses one DB transaction" just restates code behavior — this belongs in source comments, not project memory. Keep memories that explain *why* a design choice was made, *what constraint* drove it, or *what would break* if it changed.
165+
2. **Apply the archive and keep criteria above to each memory.**
129166
3. **Verify each candidate** against the codebase before archiving:
130167
- Check if the file/tool/path actually exists
131-
- Check if the feature is mentioned in current code
132-
- For USER_DIRECTIVES: verify the instructed action was completed (e.g., "License as MIT" → check LICENSE file exists)
168+
- For USER_DIRECTIVES: verify the instructed action was completed
133169
- If the reference is ambiguous, leave it alone
134170
4. **Archive** with \`ctx_memory(action="archive", id=N, reason="...")\`. Always include a specific reason.
135171
136-
### Common staleness patterns
137-
- Old plugin paths (e.g., \`oh-my-opencode\` references when the plugin is now \`magic-context\`)
138-
- Removed tools (e.g., \`ctx_recall\` was merged into \`ctx_memory\`)
139-
- Discontinued workflows (e.g., "replay onto integrate branch")
140-
- Branch-era context ("on feat/context-management")
141-
- Stale config keys or defaults that changed
142-
- Completed setup/publishing/licensing instructions that are done and won't recur
143-
- Implementation details that simply restate code behavior without adding design rationale
144-
145-
### USER_DIRECTIVES handling
146-
- **Archive** completed one-time instructions: "License as MIT", "Publish as @cortexkit/...", "Add changelog to releases", "For the README animation, emphasize X"
147-
- **Keep** ongoing preferences and behavioral rules: "Ask before changing behavior when audit finding is ambiguous", "Cache awareness is the highest-priority feature"
148-
- **Keep** workflow preferences that apply to future work: "Always use scripts/release.sh for releases"
149-
- Rule of thumb: if the directive uses imperative "do this" language and the action is done, archive it. If it describes how to behave going forward, keep it.
150-
151-
### ARCHITECTURE_DECISIONS pruning
152-
- **Archive** memories that only restate what code does: "Function X calls Y", "Module A imports B"
153-
- **Keep** memories that explain constraints, tradeoffs, or non-obvious design reasoning: "X uses Y because Z would cause cache busts"
154-
- **Keep** memories that warn about gotchas: "Don't use cwd fallback because it causes cross-project contamination"
155-
- Rule of thumb: if removing the memory would cause someone to make a wrong design decision, keep it. If it's just restating navigable code structure, archive it — ARCHITECTURE.md covers that.
172+
### Category-specific rules
173+
- **CONSTRAINTS**: archive ONLY when provably redundant with another specific constraint (not just thematically similar). Each constraint typically guards against a specific bug — losing it means the bug can return.
174+
- **USER_DIRECTIVES**: archive ONLY completed one-time tasks or exact duplicates of global user profile entries. Keep ongoing behavioral rules even if they have low retrieval.
175+
- **KNOWN_ISSUES**: NEVER archive — these prevent re-encountering bugs.
176+
- **ARCHITECTURE_DECISIONS**: archive code restatements freely, keep anything with "because", "so that", "to prevent", "to avoid".
177+
- **CONFIG_DEFAULTS**: archive bare values with no context, keep values that include rationale or prevent wrong assumptions.
156178
157179
### Success criteria
158180
- No active memories reference non-existent files, tools, or paths.
159181
- No completed one-time instructions remain in USER_DIRECTIVES.
160182
- ARCHITECTURE_DECISIONS contains design reasoning, not code restatements.
183+
- CONSTRAINTS are preserved unless provably duplicated.
161184
- Every archived memory has a specific reason.
162185
- Conservative — when in doubt, leave it active.`;
163186
}
@@ -373,6 +396,7 @@ export function buildDreamTaskPrompt(
373396
projectPath: string;
374397
lastDreamAt?: string | null;
375398
existingDocs?: { architecture: boolean; structure: boolean };
399+
userMemories?: Array<{ id: number; content: string }>;
376400
},
377401
): string {
378402
switch (task) {
@@ -381,7 +405,7 @@ export function buildDreamTaskPrompt(
381405
case "verify":
382406
return buildVerifyPrompt(args.projectPath);
383407
case "archive-stale":
384-
return buildArchiveStalePrompt(args.projectPath);
408+
return buildArchiveStalePrompt(args.projectPath, args.userMemories);
385409
case "improve":
386410
return buildImprovePrompt(args.projectPath);
387411
case "maintain-docs":

packages/plugin/src/hooks/magic-context/inject-compartments.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,33 @@ export function renderMemoryBlock(memories: Memory[]): string | null {
7474

7575
const CHARS_PER_TOKEN_ESTIMATE = 4;
7676

77+
/** Constraint keywords that signal a memory encodes a rule rather than a description. */
78+
const CONSTRAINT_KEYWORDS = /\b(must|never|always|cannot|should not|must not)\b/i;
79+
80+
/**
81+
* Assign a utility tier to a memory for injection priority.
82+
* Lower tier = higher priority (packed first).
83+
*
84+
* Tier 0: Agent actually searched for and found this memory.
85+
* Tier 1: Contains constraint/rule keywords — likely guards against a real bug.
86+
* Tier 2: Everything else.
87+
*/
88+
function utilityTier(m: Memory): number {
89+
if (m.retrievalCount > 0) return 0;
90+
if (CONSTRAINT_KEYWORDS.test(m.content)) return 1;
91+
return 2;
92+
}
93+
7794
/**
78-
* Sort memories by priority (permanent first, then higher seen_count) and trim to budget.
95+
* Sort memories by priority and trim to budget.
96+
*
97+
* Priority order:
98+
* 1. permanent status first
99+
* 2. utility tier (retrieved > constraint > other)
100+
* 3. seen count descending
101+
* 4. shorter content first (fit more memories in budget)
102+
* 5. deterministic id tiebreaker for cache stability
103+
*
79104
* Estimates ~4 chars per token for budget enforcement.
80105
*/
81106
function trimMemoriesToBudget(
@@ -87,9 +112,15 @@ function trimMemoriesToBudget(
87112
// Permanent memories first
88113
if (a.status === "permanent" && b.status !== "permanent") return -1;
89114
if (b.status === "permanent" && a.status !== "permanent") return 1;
115+
// Then by utility tier (lower = higher priority)
116+
const tierDiff = utilityTier(a) - utilityTier(b);
117+
if (tierDiff !== 0) return tierDiff;
90118
// Then by seen count descending (more frequently seen = higher priority)
91119
const seenDiff = b.seenCount - a.seenCount;
92120
if (seenDiff !== 0) return seenDiff;
121+
// Prefer shorter memories so more fit in budget
122+
const lenDiff = a.content.length - b.content.length;
123+
if (lenDiff !== 0) return lenDiff;
93124
// Deterministic tiebreaker by id to ensure stable ordering for cache safety
94125
return a.id - b.id;
95126
});

0 commit comments

Comments
 (0)