@lumenflow/memory 2.8.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -0
- package/dist/mem-recover-core.js +258 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ export * from './mem-index-core.js';
|
|
|
19
19
|
export * from './mem-promote-core.js';
|
|
20
20
|
export * from './mem-profile-core.js';
|
|
21
21
|
export * from './mem-delete-core.js';
|
|
22
|
+
export * from './mem-recover-core.js';
|
|
22
23
|
export * from './memory-schema.js';
|
|
23
24
|
export * from './memory-store.js';
|
|
24
25
|
// WU-1238: Decay scoring and access tracking
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Recovery Core (WU-1390)
|
|
3
|
+
*
|
|
4
|
+
* Generates post-compaction recovery context for agents that have lost
|
|
5
|
+
* their LumenFlow instructions due to context compaction.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Loads last checkpoint from memory layer
|
|
9
|
+
* - Extracts compact constraints from .lumenflow/constraints.md
|
|
10
|
+
* - Provides essential CLI commands reference
|
|
11
|
+
* - Size-limited output (default 2KB) to prevent truncation
|
|
12
|
+
* - Vendor-agnostic (works for any client)
|
|
13
|
+
*
|
|
14
|
+
* @see {@link packages/@lumenflow/cli/src/mem-recover.ts} - CLI wrapper
|
|
15
|
+
* @see {@link packages/@lumenflow/memory/__tests__/mem-recover-core.test.ts} - Tests
|
|
16
|
+
*/
|
|
17
|
+
import fs from 'node:fs/promises';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { loadMemory } from './memory-store.js';
|
|
20
|
+
import { MEMORY_PATTERNS } from './memory-schema.js';
|
|
21
|
+
import { LUMENFLOW_MEMORY_PATHS } from './paths.js';
|
|
22
|
+
/**
|
|
23
|
+
* Default maximum recovery context size in bytes (2KB)
|
|
24
|
+
* Smaller than spawn context to ensure it doesn't get truncated
|
|
25
|
+
*/
|
|
26
|
+
const DEFAULT_MAX_SIZE = 2048;
|
|
27
|
+
/**
|
|
28
|
+
* Node type constant for checkpoint filtering
|
|
29
|
+
* @see {@link MEMORY_NODE_TYPES} in memory-schema.ts
|
|
30
|
+
*/
|
|
31
|
+
const NODE_TYPE_CHECKPOINT = 'checkpoint';
|
|
32
|
+
/**
|
|
33
|
+
* Constraints file name within .lumenflow directory
|
|
34
|
+
*/
|
|
35
|
+
const CONSTRAINTS_FILENAME = 'constraints.md';
|
|
36
|
+
/**
|
|
37
|
+
* Default value for unknown timestamps
|
|
38
|
+
*/
|
|
39
|
+
const TIMESTAMP_UNKNOWN = 'unknown';
|
|
40
|
+
/**
|
|
41
|
+
* Error messages for validation
|
|
42
|
+
*/
|
|
43
|
+
const ERROR_MESSAGES = {
|
|
44
|
+
WU_ID_REQUIRED: 'wuId is required',
|
|
45
|
+
WU_ID_EMPTY: 'wuId cannot be empty',
|
|
46
|
+
WU_ID_INVALID: 'Invalid WU ID format. Expected pattern: WU-XXX (e.g., WU-1234)',
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Section headers for recovery context formatting
|
|
50
|
+
*/
|
|
51
|
+
const SECTION_HEADERS = {
|
|
52
|
+
RECOVERY_TITLE: 'POST-COMPACTION RECOVERY',
|
|
53
|
+
LAST_CHECKPOINT: 'Last Checkpoint',
|
|
54
|
+
CRITICAL_RULES: 'Critical Rules (DO NOT FORGET)',
|
|
55
|
+
CLI_COMMANDS: 'CLI Commands',
|
|
56
|
+
NEXT_ACTION: 'Next Action',
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Essential CLI commands for recovery (hardcoded, ~400 bytes)
|
|
60
|
+
*/
|
|
61
|
+
const ESSENTIAL_CLI_COMMANDS = `| Command | Purpose |
|
|
62
|
+
|---------|---------|
|
|
63
|
+
| pnpm wu:status --id WU-XXX | Check WU status and location |
|
|
64
|
+
| pnpm wu:spawn --id WU-XXX | Generate fresh agent spawn prompt |
|
|
65
|
+
| pnpm gates | Run quality gates before completion |
|
|
66
|
+
| pnpm mem:checkpoint | Save progress checkpoint |`;
|
|
67
|
+
/**
|
|
68
|
+
* Compact constraints (hardcoded fallback, ~800 bytes)
|
|
69
|
+
* Used when constraints.md cannot be loaded
|
|
70
|
+
*/
|
|
71
|
+
const FALLBACK_CONSTRAINTS = `1. **Worktree Discipline**: Work only in worktrees, treat main as read-only
|
|
72
|
+
2. **WUs Are Specs**: Respect code_paths boundaries, no feature creep
|
|
73
|
+
3. **Docs-Only vs Code**: Documentation WUs use --docs-only gates
|
|
74
|
+
4. **LLM-First Inference**: Use LLMs for semantic tasks, no brittle regex
|
|
75
|
+
5. **Gates Required**: Run pnpm gates before wu:done
|
|
76
|
+
6. **Safety & Governance**: No secrets in code, stop-and-ask for sensitive ops
|
|
77
|
+
7. **Test Ratchet**: NEW test failures block, pre-existing show warning only`;
|
|
78
|
+
/**
|
|
79
|
+
* Validates WU ID format
|
|
80
|
+
*/
|
|
81
|
+
function validateWuId(wuId) {
|
|
82
|
+
if (wuId === undefined || wuId === null) {
|
|
83
|
+
throw new Error(ERROR_MESSAGES.WU_ID_REQUIRED);
|
|
84
|
+
}
|
|
85
|
+
if (wuId === '') {
|
|
86
|
+
throw new Error(ERROR_MESSAGES.WU_ID_EMPTY);
|
|
87
|
+
}
|
|
88
|
+
if (!MEMORY_PATTERNS.WU_ID.test(wuId)) {
|
|
89
|
+
throw new Error(ERROR_MESSAGES.WU_ID_INVALID);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Gets the most recent checkpoint for a WU from memory
|
|
94
|
+
*/
|
|
95
|
+
async function getLastCheckpoint(baseDir, wuId) {
|
|
96
|
+
const memoryDir = path.join(baseDir, LUMENFLOW_MEMORY_PATHS.MEMORY_DIR);
|
|
97
|
+
try {
|
|
98
|
+
const memory = await loadMemory(memoryDir);
|
|
99
|
+
const wuNodes = memory.byWu.get(wuId) ?? [];
|
|
100
|
+
// Filter to checkpoint nodes and sort by timestamp (most recent first)
|
|
101
|
+
const checkpoints = wuNodes
|
|
102
|
+
.filter((node) => node.type === NODE_TYPE_CHECKPOINT)
|
|
103
|
+
.sort((a, b) => {
|
|
104
|
+
const aTime = new Date(a.created_at).getTime();
|
|
105
|
+
const bTime = new Date(b.created_at).getTime();
|
|
106
|
+
return bTime - aTime; // Most recent first
|
|
107
|
+
});
|
|
108
|
+
if (checkpoints.length === 0) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const latest = checkpoints[0];
|
|
112
|
+
return {
|
|
113
|
+
content: latest.content,
|
|
114
|
+
timestamp: latest.created_at,
|
|
115
|
+
progress: latest.metadata?.progress,
|
|
116
|
+
nextSteps: latest.metadata?.nextSteps,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Memory layer not initialized or error loading
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Loads compact constraints from .lumenflow/constraints.md
|
|
126
|
+
* Extracts just the rule summary from each of the 7 sections
|
|
127
|
+
*/
|
|
128
|
+
async function loadCompactConstraints(baseDir) {
|
|
129
|
+
const constraintsPath = path.join(baseDir, LUMENFLOW_MEMORY_PATHS.BASE, CONSTRAINTS_FILENAME);
|
|
130
|
+
try {
|
|
131
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
132
|
+
const content = await fs.readFile(constraintsPath, 'utf-8');
|
|
133
|
+
// Extract section headers and their Rule lines
|
|
134
|
+
const rules = [];
|
|
135
|
+
const sectionPattern = /### (\d+)\. ([^\n]+)\n\n\*\*Rule:\*\* ([^\n]+)/g;
|
|
136
|
+
let match;
|
|
137
|
+
while ((match = sectionPattern.exec(content)) !== null) {
|
|
138
|
+
const [, num, title, rule] = match;
|
|
139
|
+
rules.push(`${num}. **${title}**: ${rule}`);
|
|
140
|
+
}
|
|
141
|
+
if (rules.length > 0) {
|
|
142
|
+
return rules.join('\n');
|
|
143
|
+
}
|
|
144
|
+
// Fallback if parsing failed
|
|
145
|
+
return FALLBACK_CONSTRAINTS;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// File not found or read error - use fallback
|
|
149
|
+
return FALLBACK_CONSTRAINTS;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Formats the recovery prompt with size budget
|
|
154
|
+
*/
|
|
155
|
+
function formatRecoveryPrompt(wuId, checkpoint, constraints, cliRef, maxSize) {
|
|
156
|
+
const header = `# ${SECTION_HEADERS.RECOVERY_TITLE}
|
|
157
|
+
|
|
158
|
+
You are resuming work after context compaction. Your previous context was lost.
|
|
159
|
+
**WU:** ${wuId}
|
|
160
|
+
|
|
161
|
+
`;
|
|
162
|
+
const checkpointSection = checkpoint
|
|
163
|
+
? `## ${SECTION_HEADERS.LAST_CHECKPOINT}
|
|
164
|
+
- **Progress:** ${checkpoint.content}
|
|
165
|
+
- **Timestamp:** ${checkpoint.timestamp}
|
|
166
|
+
${checkpoint.nextSteps ? `- **Next Steps:** ${checkpoint.nextSteps}` : ''}
|
|
167
|
+
|
|
168
|
+
`
|
|
169
|
+
: `## ${SECTION_HEADERS.LAST_CHECKPOINT}
|
|
170
|
+
No checkpoint found for this WU.
|
|
171
|
+
|
|
172
|
+
`;
|
|
173
|
+
// Only include sections if they have content
|
|
174
|
+
const constraintsSection = constraints
|
|
175
|
+
? `## ${SECTION_HEADERS.CRITICAL_RULES}
|
|
176
|
+
${constraints}
|
|
177
|
+
|
|
178
|
+
`
|
|
179
|
+
: '';
|
|
180
|
+
const cliSection = cliRef
|
|
181
|
+
? `## ${SECTION_HEADERS.CLI_COMMANDS}
|
|
182
|
+
${cliRef}
|
|
183
|
+
|
|
184
|
+
`
|
|
185
|
+
: '';
|
|
186
|
+
const footer = `## ${SECTION_HEADERS.NEXT_ACTION}
|
|
187
|
+
Run \`pnpm wu:spawn --id ${wuId}\` to spawn a fresh agent with full context.
|
|
188
|
+
`;
|
|
189
|
+
// Build sections in priority order
|
|
190
|
+
// Priority: header > checkpoint > constraints > CLI > footer
|
|
191
|
+
// If over budget, truncate checkpoint content first
|
|
192
|
+
const fixedContent = header + constraintsSection + cliSection + footer;
|
|
193
|
+
const fixedSize = Buffer.byteLength(fixedContent, 'utf-8');
|
|
194
|
+
if (fixedSize > maxSize) {
|
|
195
|
+
// Even fixed content exceeds budget - return minimal recovery
|
|
196
|
+
const minimal = `# ${SECTION_HEADERS.RECOVERY_TITLE}
|
|
197
|
+
WU: ${wuId}
|
|
198
|
+
Run: pnpm wu:spawn --id ${wuId}
|
|
199
|
+
`;
|
|
200
|
+
return { context: minimal, truncated: true };
|
|
201
|
+
}
|
|
202
|
+
const remainingBudget = maxSize - fixedSize;
|
|
203
|
+
const checkpointSize = Buffer.byteLength(checkpointSection, 'utf-8');
|
|
204
|
+
if (checkpointSize <= remainingBudget) {
|
|
205
|
+
// Full checkpoint fits
|
|
206
|
+
const fullContext = header + checkpointSection + constraintsSection + cliSection + footer;
|
|
207
|
+
return { context: fullContext, truncated: false };
|
|
208
|
+
}
|
|
209
|
+
// Truncate checkpoint to fit
|
|
210
|
+
const truncatedCheckpoint = `## ${SECTION_HEADERS.LAST_CHECKPOINT}
|
|
211
|
+
- **Progress:** (truncated - run mem:ready --wu ${wuId} for details)
|
|
212
|
+
- **Timestamp:** ${checkpoint?.timestamp ?? TIMESTAMP_UNKNOWN}
|
|
213
|
+
|
|
214
|
+
`;
|
|
215
|
+
const context = header + truncatedCheckpoint + constraintsSection + cliSection + footer;
|
|
216
|
+
return { context, truncated: true };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Generates post-compaction recovery context for an agent.
|
|
220
|
+
*
|
|
221
|
+
* The recovery context includes:
|
|
222
|
+
* - Last checkpoint for the WU (from memory layer)
|
|
223
|
+
* - Compact constraints (7 rules from constraints.md)
|
|
224
|
+
* - Essential CLI commands reference
|
|
225
|
+
* - Guidance to spawn fresh agent
|
|
226
|
+
*
|
|
227
|
+
* Size is limited to prevent the recovery context itself from being
|
|
228
|
+
* truncated or compacted.
|
|
229
|
+
*
|
|
230
|
+
* @param options - Recovery options
|
|
231
|
+
* @returns Recovery context result
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* const result = await generateRecoveryContext({
|
|
235
|
+
* wuId: 'WU-1390',
|
|
236
|
+
* maxSize: 2048,
|
|
237
|
+
* });
|
|
238
|
+
* console.log(result.context);
|
|
239
|
+
*/
|
|
240
|
+
export async function generateRecoveryContext(options) {
|
|
241
|
+
const { wuId, baseDir = '.', maxSize = DEFAULT_MAX_SIZE, includeConstraints = true, includeCLIRef = true, } = options;
|
|
242
|
+
// Validate WU ID
|
|
243
|
+
validateWuId(wuId);
|
|
244
|
+
// Load checkpoint from memory
|
|
245
|
+
const checkpoint = await getLastCheckpoint(baseDir, wuId);
|
|
246
|
+
// Load constraints (or use empty if disabled)
|
|
247
|
+
const constraints = includeConstraints ? await loadCompactConstraints(baseDir) : '';
|
|
248
|
+
// Get CLI reference (or empty if disabled)
|
|
249
|
+
const cliRef = includeCLIRef ? ESSENTIAL_CLI_COMMANDS : '';
|
|
250
|
+
// Format with size budget
|
|
251
|
+
const { context, truncated } = formatRecoveryPrompt(wuId, checkpoint, constraints, cliRef, maxSize);
|
|
252
|
+
return {
|
|
253
|
+
success: true,
|
|
254
|
+
context,
|
|
255
|
+
size: Buffer.byteLength(context, 'utf-8'),
|
|
256
|
+
truncated,
|
|
257
|
+
};
|
|
258
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumenflow/memory",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "Memory layer for LumenFlow workflow framework - session tracking, context recovery, and agent coordination",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lumenflow",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"ms": "^2.1.3",
|
|
50
50
|
"yaml": "^2.8.2",
|
|
51
51
|
"zod": "^4.3.5",
|
|
52
|
-
"@lumenflow/core": "2.
|
|
52
|
+
"@lumenflow/core": "2.10.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@vitest/coverage-v8": "^4.0.17",
|