@fyso/awareness-framework 0.2.0 → 0.3.1

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.
@@ -0,0 +1,1026 @@
1
+ # Local Memory Operations Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Add a small Cognee-inspired local memory operation model to Awareness: `remember`, `recall`, `forget`, and `improve`.
6
+
7
+ **Architecture:** Keep Markdown files as human-readable projections and add an append-only local event log at `memory/events.jsonl` for auditability. Implement deterministic text recall across memory, worklog, evaluations, and memory events before considering embeddings, graph storage, or additional services.
8
+
9
+ **Tech Stack:** Node.js ESM, built-in `fs`, `path`, `os`, current CLI parser in `src/cli.js`, Node test runner in `test/cli.test.js`, Markdown docs/templates.
10
+
11
+ ## Global Constraints
12
+
13
+ - No graph database.
14
+ - No embeddings or vector store.
15
+ - No external services.
16
+ - No new runtime dependencies.
17
+ - Keep private operational state under `~/.agents`.
18
+ - Keep Markdown as the readable projection.
19
+ - Durable memory promotion remains explicit and evidence-backed.
20
+ - Forget means prune/revise, not destructive deletion.
21
+
22
+ ---
23
+
24
+ ## File Structure
25
+
26
+ - Modify `src/cli.js`: add top-level command routing, event-log helpers, `remember`, `recall`, `forget`, and `improve` command implementations.
27
+ - Modify `test/cli.test.js`: add focused CLI tests for events, remember, recall, forget, and improve.
28
+ - Modify `docs/cli.md`: document the new commands and event-log behavior.
29
+ - Modify `docs/memory.md`: explain event log plus Markdown projection model.
30
+ - Modify `README.md`: update quick start and private layout to mention `memory/events.jsonl` and memory operations.
31
+ - Modify `templates/agent-instructions.md`: instruct agents when to use remember, recall, forget, and improve.
32
+ - Modify `templates/memory-long-term.md`: describe event-backed projection and prune/revision behavior.
33
+
34
+ ---
35
+
36
+ ### Task 1: Add Top-Level Memory Operation Routing
37
+
38
+ **Files:**
39
+ - Modify: `src/cli.js`
40
+ - Test: `test/cli.test.js`
41
+
42
+ **Interfaces:**
43
+ - Consumes: existing `runCli(argv, options)`, `parseArgs(argv)`, `printHelp(ctx)`.
44
+ - Produces: top-level commands `remember`, `recall`, `forget`, and `improve`; positional tail support for `recall QUERY`.
45
+
46
+ - [ ] **Step 1: Write failing test for help output**
47
+
48
+ Add this test near the other CLI command tests in `test/cli.test.js`:
49
+
50
+ ```js
51
+ test('help lists local memory operation commands', () => {
52
+ let stdout = '';
53
+ const code = runCli(['help'], {
54
+ env: {
55
+ ...process.env,
56
+ AWARENESS_NOW: '2099-01-02T12:34:00.000Z',
57
+ },
58
+ stdout: { write: (chunk) => { stdout += chunk; } },
59
+ stderr: { write: () => {} },
60
+ });
61
+
62
+ assert.equal(code, 0);
63
+ assert.match(stdout, /awareness remember --text TEXT --evidence TEXT/);
64
+ assert.match(stdout, /awareness recall QUERY/);
65
+ assert.match(stdout, /awareness forget --text TEXT --reason TEXT --evidence TEXT/);
66
+ assert.match(stdout, /awareness improve/);
67
+ });
68
+ ```
69
+
70
+ - [ ] **Step 2: Run test to verify it fails**
71
+
72
+ Run: `npm test -- --test-name-pattern "help lists local memory operation commands"`
73
+
74
+ Expected: FAIL because help output does not include these commands.
75
+
76
+ - [ ] **Step 3: Update positional parsing and command routing**
77
+
78
+ In `src/cli.js`, change the positional destructuring in `runCli` from:
79
+
80
+ ```js
81
+ const [command, subcommand] = parsed.positionals;
82
+ ```
83
+
84
+ to:
85
+
86
+ ```js
87
+ const [command, subcommand, ...positionRest] = parsed.positionals;
88
+ ```
89
+
90
+ Add these cases to the command switch after the existing `memory` case:
91
+
92
+ ```js
93
+ case 'remember':
94
+ return rememberCommand(ctx, parsed.opts);
95
+ case 'recall':
96
+ return recallCommand(ctx, [subcommand, ...positionRest].filter(Boolean).join(' '), parsed.opts);
97
+ case 'forget':
98
+ return forgetCommand(ctx, parsed.opts);
99
+ case 'improve':
100
+ return improveCommand(ctx, parsed.opts);
101
+ ```
102
+
103
+ Add these help lines after the `awareness memory promote ...` line in `printHelp(ctx)`:
104
+
105
+ ```text
106
+ awareness remember --text TEXT --evidence TEXT [--home PATH]
107
+ awareness recall QUERY [--limit N] [--home PATH]
108
+ awareness forget --text TEXT --reason TEXT --evidence TEXT [--home PATH]
109
+ awareness improve [--force] [--min-count N] [--home PATH]
110
+ ```
111
+
112
+ Add temporary command stubs below `memoryPromotionSection(kind)` so routing can compile:
113
+
114
+ ```js
115
+ function rememberCommand(ctx, opts) {
116
+ throw new Error('remember command is not implemented yet');
117
+ }
118
+
119
+ function recallCommand(ctx, query, opts) {
120
+ throw new Error('recall command is not implemented yet');
121
+ }
122
+
123
+ function forgetCommand(ctx, opts) {
124
+ throw new Error('forget command is not implemented yet');
125
+ }
126
+
127
+ function improveCommand(ctx, opts) {
128
+ throw new Error('improve command is not implemented yet');
129
+ }
130
+ ```
131
+
132
+ - [ ] **Step 4: Run test to verify it passes**
133
+
134
+ Run: `npm test -- --test-name-pattern "help lists local memory operation commands"`
135
+
136
+ Expected: PASS.
137
+
138
+ - [ ] **Step 5: Run full tests**
139
+
140
+ Run: `npm test`
141
+
142
+ Expected: PASS for the existing suite plus the new help test.
143
+
144
+ - [ ] **Step 6: Commit**
145
+
146
+ ```bash
147
+ git add src/cli.js test/cli.test.js
148
+ git commit -m "Add memory operation command routing"
149
+ ```
150
+
151
+ ---
152
+
153
+ ### Task 2: Add Append-Only Memory Event Log Helpers
154
+
155
+ **Files:**
156
+ - Modify: `src/cli.js`
157
+ - Test: `test/cli.test.js`
158
+
159
+ **Interfaces:**
160
+ - Consumes: `todayParts(ctx)`, `formatTimestamp(today)`, `ensureDir(dir)`.
161
+ - Produces: `memoryEventPath(home)`, `appendMemoryEvent(home, today, event)`, `readMemoryEvents(home)`.
162
+
163
+ - [ ] **Step 1: Write failing tests for event log writes from existing memory commands**
164
+
165
+ Add this test after `memory note and promote update long-term memory`:
166
+
167
+ ```js
168
+ test('memory note and promote append auditable memory events', () => {
169
+ const home = tempHome();
170
+ run(['init'], home);
171
+
172
+ run([
173
+ 'memory',
174
+ 'note',
175
+ '--text', 'User wants local recall',
176
+ '--evidence', 'Planning discussion',
177
+ ], home);
178
+ run([
179
+ 'memory',
180
+ 'promote',
181
+ '--kind', 'preference',
182
+ '--text', 'Prefer local-first memory operations',
183
+ '--evidence', 'User approved local event log design',
184
+ ], home);
185
+
186
+ const events = fs.readFileSync(path.join(home, 'memory', 'events.jsonl'), 'utf8')
187
+ .trim()
188
+ .split('\n')
189
+ .map((line) => JSON.parse(line));
190
+
191
+ assert.equal(events[0].type, 'memory.candidate.created');
192
+ assert.equal(events[0].text, 'User wants local recall');
193
+ assert.equal(events[0].source, 'memory.note');
194
+ assert.equal(events[1].type, 'memory.promoted');
195
+ assert.equal(events[1].kind, 'preference');
196
+ assert.equal(events[1].text, 'Prefer local-first memory operations');
197
+ });
198
+ ```
199
+
200
+ - [ ] **Step 2: Run test to verify it fails**
201
+
202
+ Run: `npm test -- --test-name-pattern "memory note and promote append auditable memory events"`
203
+
204
+ Expected: FAIL because `memory/events.jsonl` does not exist.
205
+
206
+ - [ ] **Step 3: Add event log helpers**
207
+
208
+ Add these helper functions near `longTermMemoryPath(home)`:
209
+
210
+ ```js
211
+ function memoryEventPath(home) {
212
+ return path.join(home, 'memory', 'events.jsonl');
213
+ }
214
+
215
+ function appendMemoryEvent(home, today, event) {
216
+ const file = memoryEventPath(home);
217
+ ensureDir(path.dirname(file));
218
+ fs.appendFileSync(file, `${JSON.stringify({
219
+ timestamp: formatTimestamp(today),
220
+ ...event,
221
+ })}\n`);
222
+ return file;
223
+ }
224
+
225
+ function readMemoryEvents(home) {
226
+ const file = memoryEventPath(home);
227
+ if (!fs.existsSync(file)) return [];
228
+ return fs.readFileSync(file, 'utf8')
229
+ .split('\n')
230
+ .map((line) => line.trim())
231
+ .filter(Boolean)
232
+ .map((line) => JSON.parse(line));
233
+ }
234
+ ```
235
+
236
+ - [ ] **Step 4: Update candidate and promotion writers to append events**
237
+
238
+ Change `appendMemoryCandidate` signature from:
239
+
240
+ ```js
241
+ function appendMemoryCandidate(home, today, text, evidence) {
242
+ ```
243
+
244
+ to:
245
+
246
+ ```js
247
+ function appendMemoryCandidate(home, today, text, evidence, source = 'memory.note') {
248
+ ```
249
+
250
+ After `fs.writeFileSync(file, content);`, add:
251
+
252
+ ```js
253
+ appendMemoryEvent(home, today, {
254
+ type: 'memory.candidate.created',
255
+ source,
256
+ text,
257
+ evidence,
258
+ });
259
+ ```
260
+
261
+ In `recordEvaluationMemoryCandidates(home, today)`, change:
262
+
263
+ ```js
264
+ return candidates.filter((candidate) => appendMemoryCandidate(home, today, candidate.text, candidate.evidence));
265
+ ```
266
+
267
+ to:
268
+
269
+ ```js
270
+ return candidates.filter((candidate) => appendMemoryCandidate(home, today, candidate.text, candidate.evidence, 'evaluation'));
271
+ ```
272
+
273
+ In `memoryPromoteCommand`, after `fs.writeFileSync(file, content);`, add:
274
+
275
+ ```js
276
+ appendMemoryEvent(home, today, {
277
+ type: 'memory.promoted',
278
+ kind,
279
+ section,
280
+ text,
281
+ evidence,
282
+ });
283
+ ```
284
+
285
+ - [ ] **Step 5: Run test to verify it passes**
286
+
287
+ Run: `npm test -- --test-name-pattern "memory note and promote append auditable memory events"`
288
+
289
+ Expected: PASS.
290
+
291
+ - [ ] **Step 6: Run full tests**
292
+
293
+ Run: `npm test`
294
+
295
+ Expected: PASS.
296
+
297
+ - [ ] **Step 7: Commit**
298
+
299
+ ```bash
300
+ git add src/cli.js test/cli.test.js
301
+ git commit -m "Record auditable memory events"
302
+ ```
303
+
304
+ ---
305
+
306
+ ### Task 3: Implement `awareness remember`
307
+
308
+ **Files:**
309
+ - Modify: `src/cli.js`
310
+ - Test: `test/cli.test.js`
311
+
312
+ **Interfaces:**
313
+ - Consumes: `appendMemoryCandidate(home, today, text, evidence, source)`, `agentsHome(ctx, opts)`, `ensurePrivateState(home, ctx)`.
314
+ - Produces: top-level `awareness remember --text TEXT --evidence TEXT`.
315
+
316
+ - [ ] **Step 1: Write failing test**
317
+
318
+ Add this test after the event-log test:
319
+
320
+ ```js
321
+ test('remember records a promotion candidate and event', () => {
322
+ const home = tempHome();
323
+ run(['init'], home);
324
+
325
+ const result = run([
326
+ 'remember',
327
+ '--text', 'Prefer recall before repeating implementation work',
328
+ '--evidence', 'User asked for active memory operations',
329
+ ], home);
330
+
331
+ assert.equal(result.code, 0);
332
+ assert.match(result.stdout, /Remembered candidate/);
333
+
334
+ const memory = fs.readFileSync(path.join(home, 'memory', 'long-term.md'), 'utf8');
335
+ assert.match(memory, /Prefer recall before repeating implementation work/);
336
+
337
+ const [event] = fs.readFileSync(path.join(home, 'memory', 'events.jsonl'), 'utf8')
338
+ .trim()
339
+ .split('\n')
340
+ .map((line) => JSON.parse(line));
341
+ assert.equal(event.type, 'memory.candidate.created');
342
+ assert.equal(event.source, 'remember');
343
+ });
344
+ ```
345
+
346
+ - [ ] **Step 2: Run test to verify it fails**
347
+
348
+ Run: `npm test -- --test-name-pattern "remember records a promotion candidate and event"`
349
+
350
+ Expected: FAIL with `remember command is not implemented yet`.
351
+
352
+ - [ ] **Step 3: Implement remember command**
353
+
354
+ Replace the `rememberCommand` stub with:
355
+
356
+ ```js
357
+ function rememberCommand(ctx, opts) {
358
+ const home = agentsHome(ctx, opts);
359
+ ensurePrivateState(home, ctx);
360
+ const text = required(opts, 'text');
361
+ const evidence = required(opts, 'evidence');
362
+ const today = todayParts(ctx);
363
+ const added = appendMemoryCandidate(home, today, text, evidence, 'remember');
364
+ out(ctx, added ? `Remembered candidate: ${text}` : `Memory candidate already exists: ${text}`);
365
+ return 0;
366
+ }
367
+ ```
368
+
369
+ - [ ] **Step 4: Run test to verify it passes**
370
+
371
+ Run: `npm test -- --test-name-pattern "remember records a promotion candidate and event"`
372
+
373
+ Expected: PASS.
374
+
375
+ - [ ] **Step 5: Run full tests**
376
+
377
+ Run: `npm test`
378
+
379
+ Expected: PASS.
380
+
381
+ - [ ] **Step 6: Commit**
382
+
383
+ ```bash
384
+ git add src/cli.js test/cli.test.js
385
+ git commit -m "Add remember command"
386
+ ```
387
+
388
+ ---
389
+
390
+ ### Task 4: Implement Deterministic `awareness recall`
391
+
392
+ **Files:**
393
+ - Modify: `src/cli.js`
394
+ - Test: `test/cli.test.js`
395
+
396
+ **Interfaces:**
397
+ - Consumes: `longTermMemoryPath(home)`, `memoryEventPath(home)`, `path.join(home, 'worklog')`, `path.join(home, 'evaluations')`.
398
+ - Produces: `awareness recall QUERY [--limit N]` with deterministic text matching.
399
+
400
+ - [ ] **Step 1: Write failing recall test**
401
+
402
+ Add this test after the remember test:
403
+
404
+ ```js
405
+ test('recall searches memory, events, worklogs, and evaluations', () => {
406
+ const home = tempHome();
407
+ run(['init'], home);
408
+ run([
409
+ 'remember',
410
+ '--text', 'Always run recall before implementing memory features',
411
+ '--evidence', 'Memory operations plan',
412
+ ], home);
413
+ run([
414
+ 'log',
415
+ '--task', 'PROJECT-123',
416
+ '--summary', 'Validated recall behavior',
417
+ '--changes', 'Recall should search worklog text.',
418
+ '--evidence', 'test/cli.test.js',
419
+ ], home);
420
+
421
+ const result = run(['recall', 'recall behavior'], home);
422
+
423
+ assert.equal(result.code, 0);
424
+ assert.match(result.stdout, /Recall Results/);
425
+ assert.match(result.stdout, /memory\/long-term\.md/);
426
+ assert.match(result.stdout, /worklog\/2099-01-02\.md/);
427
+ });
428
+ ```
429
+
430
+ - [ ] **Step 2: Run test to verify it fails**
431
+
432
+ Run: `npm test -- --test-name-pattern "recall searches memory"`
433
+
434
+ Expected: FAIL with `recall command is not implemented yet`.
435
+
436
+ - [ ] **Step 3: Add recall helpers**
437
+
438
+ Add these helpers near `readMemoryEvents(home)`:
439
+
440
+ ```js
441
+ function collectRecallSources(home) {
442
+ return [
443
+ longTermMemoryPath(home),
444
+ memoryEventPath(home),
445
+ ...markdownFiles(path.join(home, 'worklog')),
446
+ ...markdownFiles(path.join(home, 'evaluations')),
447
+ ].filter((file) => fs.existsSync(file));
448
+ }
449
+
450
+ function markdownFiles(dir) {
451
+ if (!fs.existsSync(dir)) return [];
452
+ return fs.readdirSync(dir)
453
+ .filter((name) => name.endsWith('.md'))
454
+ .sort()
455
+ .map((name) => path.join(dir, name));
456
+ }
457
+
458
+ function recallMatches(home, query, limit) {
459
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
460
+ const results = [];
461
+ for (const file of collectRecallSources(home)) {
462
+ const content = fs.readFileSync(file, 'utf8');
463
+ const lines = content.split('\n');
464
+ lines.forEach((line, index) => {
465
+ const haystack = line.toLowerCase();
466
+ const score = terms.filter((term) => haystack.includes(term)).length;
467
+ if (score > 0) {
468
+ results.push({
469
+ file,
470
+ line: index + 1,
471
+ score,
472
+ text: line.trim(),
473
+ });
474
+ }
475
+ });
476
+ }
477
+ return results
478
+ .sort((left, right) => right.score - left.score || left.file.localeCompare(right.file) || left.line - right.line)
479
+ .slice(0, limit);
480
+ }
481
+ ```
482
+
483
+ - [ ] **Step 4: Implement recall command**
484
+
485
+ Replace the `recallCommand` stub with:
486
+
487
+ ```js
488
+ function recallCommand(ctx, query, opts) {
489
+ const home = agentsHome(ctx, opts);
490
+ ensurePrivateState(home, ctx);
491
+ const search = opts.query || query;
492
+ if (!search || search === true) {
493
+ throw new Error('Missing recall query. Use: awareness recall QUERY');
494
+ }
495
+ const limit = Number.parseInt(opts.limit || '10', 10);
496
+ if (!Number.isInteger(limit) || limit < 1) {
497
+ throw new Error('Invalid --limit. Use an integer >= 1.');
498
+ }
499
+
500
+ const results = recallMatches(home, search, limit);
501
+ out(ctx, `Recall Results (${results.length})`);
502
+ if (!results.length) {
503
+ out(ctx, '- No matches.');
504
+ return 0;
505
+ }
506
+
507
+ for (const result of results) {
508
+ out(ctx, `- ${displayPath(home, result.file)}:${result.line}: ${result.text}`);
509
+ }
510
+ return 0;
511
+ }
512
+ ```
513
+
514
+ - [ ] **Step 5: Run recall test**
515
+
516
+ Run: `npm test -- --test-name-pattern "recall searches memory"`
517
+
518
+ Expected: PASS.
519
+
520
+ - [ ] **Step 6: Run full tests**
521
+
522
+ Run: `npm test`
523
+
524
+ Expected: PASS.
525
+
526
+ - [ ] **Step 7: Commit**
527
+
528
+ ```bash
529
+ git add src/cli.js test/cli.test.js
530
+ git commit -m "Add deterministic recall command"
531
+ ```
532
+
533
+ ---
534
+
535
+ ### Task 5: Implement Non-Destructive `awareness forget`
536
+
537
+ **Files:**
538
+ - Modify: `src/cli.js`
539
+ - Test: `test/cli.test.js`
540
+
541
+ **Interfaces:**
542
+ - Consumes: `appendToSection(content, section, addition)`, `replaceMetadata(content, key, value)`, `appendMemoryEvent(home, today, event)`.
543
+ - Produces: `awareness forget --text TEXT --reason TEXT --evidence TEXT`.
544
+
545
+ - [ ] **Step 1: Write failing forget test**
546
+
547
+ Add this test after the recall test:
548
+
549
+ ```js
550
+ test('forget records a pruned memory without deleting history', () => {
551
+ const home = tempHome();
552
+ run(['init'], home);
553
+ run([
554
+ 'remember',
555
+ '--text', 'Temporary memory to revise',
556
+ '--evidence', 'Initial observation',
557
+ ], home);
558
+
559
+ const result = run([
560
+ 'forget',
561
+ '--text', 'Temporary memory to revise',
562
+ '--reason', 'Superseded by explicit user correction',
563
+ '--evidence', 'User correction',
564
+ ], home);
565
+
566
+ assert.equal(result.code, 0);
567
+ assert.match(result.stdout, /Memory pruned or revised/);
568
+
569
+ const memory = fs.readFileSync(path.join(home, 'memory', 'long-term.md'), 'utf8');
570
+ assert.match(memory, /## Pruned Or Revised/);
571
+ assert.match(memory, /Temporary memory to revise/);
572
+ assert.match(memory, /Superseded by explicit user correction/);
573
+
574
+ const events = fs.readFileSync(path.join(home, 'memory', 'events.jsonl'), 'utf8')
575
+ .trim()
576
+ .split('\n')
577
+ .map((line) => JSON.parse(line));
578
+ assert.equal(events.at(-1).type, 'memory.pruned');
579
+ });
580
+ ```
581
+
582
+ - [ ] **Step 2: Run test to verify it fails**
583
+
584
+ Run: `npm test -- --test-name-pattern "forget records a pruned memory"`
585
+
586
+ Expected: FAIL with `forget command is not implemented yet`.
587
+
588
+ - [ ] **Step 3: Implement forget command**
589
+
590
+ Replace the `forgetCommand` stub with:
591
+
592
+ ```js
593
+ function forgetCommand(ctx, opts) {
594
+ const home = agentsHome(ctx, opts);
595
+ ensurePrivateState(home, ctx);
596
+ const text = required(opts, 'text');
597
+ const reason = required(opts, 'reason');
598
+ const evidence = required(opts, 'evidence');
599
+ const today = todayParts(ctx);
600
+ const file = longTermMemoryPath(home);
601
+ let content = fs.readFileSync(file, 'utf8');
602
+ content = replaceMetadata(content, 'Updated', formatTimestamp(today));
603
+ content = appendToSection(content, 'Pruned Or Revised', `- ${today.date}: ${text} (reason: ${reason}; evidence: ${evidence})\n`);
604
+ fs.writeFileSync(file, content);
605
+ appendMemoryEvent(home, today, {
606
+ type: 'memory.pruned',
607
+ text,
608
+ reason,
609
+ evidence,
610
+ });
611
+ out(ctx, `Memory pruned or revised: ${text}`);
612
+ return 0;
613
+ }
614
+ ```
615
+
616
+ - [ ] **Step 4: Run forget test**
617
+
618
+ Run: `npm test -- --test-name-pattern "forget records a pruned memory"`
619
+
620
+ Expected: PASS.
621
+
622
+ - [ ] **Step 5: Run full tests**
623
+
624
+ Run: `npm test`
625
+
626
+ Expected: PASS.
627
+
628
+ - [ ] **Step 6: Commit**
629
+
630
+ ```bash
631
+ git add src/cli.js test/cli.test.js
632
+ git commit -m "Add non-destructive forget command"
633
+ ```
634
+
635
+ ---
636
+
637
+ ### Task 6: Implement `awareness improve`
638
+
639
+ **Files:**
640
+ - Modify: `src/cli.js`
641
+ - Test: `test/cli.test.js`
642
+
643
+ **Interfaces:**
644
+ - Consumes: `buildEvaluation(home, today)`, `writeEvaluationIfMissing(home, today)`, `recordEvaluationMemoryCandidates(home, today)`, `repeatedMemoryCandidateSuggestions(content, minCount)`, `appendMemoryEvent(home, today, event)`.
645
+ - Produces: `awareness improve [--force] [--min-count N]`.
646
+
647
+ - [ ] **Step 1: Write failing improve test**
648
+
649
+ Add this test after the forget test:
650
+
651
+ ```js
652
+ test('improve writes evaluation and surfaces repeated pattern suggestions', () => {
653
+ const home = tempHome();
654
+ run(['init'], home);
655
+ run([
656
+ 'remember',
657
+ '--text', 'Improve traceability before handoff',
658
+ '--evidence', 'worklog/2099-01-01.md',
659
+ ], home, { AWARENESS_NOW: '2099-01-01T12:34:00.000Z' });
660
+ run([
661
+ 'remember',
662
+ '--text', 'Improve traceability before handoff',
663
+ '--evidence', 'worklog/2099-01-02.md',
664
+ ], home);
665
+
666
+ const result = run(['improve'], home);
667
+
668
+ assert.equal(result.code, 0);
669
+ assert.match(result.stdout, /Evaluation:/);
670
+ assert.match(result.stdout, /Pattern suggestions: 1/);
671
+ assert.match(result.stdout, /Improve traceability before handoff/);
672
+ assert.equal(fs.existsSync(path.join(home, 'evaluations', '2099-01-02.md')), true);
673
+
674
+ const events = fs.readFileSync(path.join(home, 'memory', 'events.jsonl'), 'utf8')
675
+ .trim()
676
+ .split('\n')
677
+ .map((line) => JSON.parse(line));
678
+ assert.equal(events.at(-1).type, 'pattern.suggested');
679
+ });
680
+ ```
681
+
682
+ - [ ] **Step 2: Run test to verify it fails**
683
+
684
+ Run: `npm test -- --test-name-pattern "improve writes evaluation"`
685
+
686
+ Expected: FAIL with `improve command is not implemented yet`.
687
+
688
+ - [ ] **Step 3: Implement improve command**
689
+
690
+ Replace the `improveCommand` stub with:
691
+
692
+ ```js
693
+ function improveCommand(ctx, opts) {
694
+ const home = agentsHome(ctx, opts);
695
+ ensurePrivateState(home, ctx);
696
+ const today = todayParts(ctx);
697
+ const evaluationPath = path.join(home, 'evaluations', `${today.date}.md`);
698
+ const force = Boolean(opts.force);
699
+
700
+ let evaluation;
701
+ if (force && fs.existsSync(evaluationPath)) {
702
+ fs.writeFileSync(evaluationPath, buildEvaluation(home, today));
703
+ const candidates = recordEvaluationMemoryCandidates(home, today);
704
+ evaluation = { file: evaluationPath, status: 'rewritten', candidates };
705
+ } else {
706
+ evaluation = writeEvaluationIfMissing(home, today);
707
+ }
708
+
709
+ appendMemoryEvent(home, today, {
710
+ type: 'evaluation.created',
711
+ file: evaluation.file,
712
+ status: evaluation.status,
713
+ });
714
+
715
+ const minCount = Number.parseInt(opts.minCount || '2', 10);
716
+ if (!Number.isInteger(minCount) || minCount < 2) {
717
+ throw new Error('Invalid --min-count. Use an integer >= 2.');
718
+ }
719
+
720
+ const content = fs.readFileSync(longTermMemoryPath(home), 'utf8');
721
+ const suggestions = repeatedMemoryCandidateSuggestions(content, minCount);
722
+ for (const suggestion of suggestions) {
723
+ appendMemoryEvent(home, today, {
724
+ type: 'pattern.suggested',
725
+ text: suggestion.text,
726
+ count: suggestion.count,
727
+ evidence: suggestion.evidence,
728
+ });
729
+ }
730
+
731
+ out(ctx, `Evaluation: ${evaluation.status} (${evaluation.file})`);
732
+ out(ctx, `Memory candidates: ${evaluation.candidates ? evaluation.candidates.length : 'not changed'}`);
733
+ out(ctx, `Pattern suggestions: ${suggestions.length}`);
734
+ for (const suggestion of suggestions) {
735
+ out(ctx, `- ${suggestion.text} (${suggestion.count} observations)`);
736
+ out(ctx, ` Promote: awareness memory promote --kind pattern --text "${shellQuoteText(suggestion.text)}" --evidence "${shellQuoteText(suggestion.evidence)}"`);
737
+ }
738
+ return 0;
739
+ }
740
+ ```
741
+
742
+ - [ ] **Step 4: Run improve test**
743
+
744
+ Run: `npm test -- --test-name-pattern "improve writes evaluation"`
745
+
746
+ Expected: PASS.
747
+
748
+ - [ ] **Step 5: Run full tests**
749
+
750
+ Run: `npm test`
751
+
752
+ Expected: PASS.
753
+
754
+ - [ ] **Step 6: Commit**
755
+
756
+ ```bash
757
+ git add src/cli.js test/cli.test.js
758
+ git commit -m "Add improve command"
759
+ ```
760
+
761
+ ---
762
+
763
+ ### Task 7: Update Docs, README, and Templates
764
+
765
+ **Files:**
766
+ - Modify: `docs/cli.md`
767
+ - Modify: `docs/memory.md`
768
+ - Modify: `README.md`
769
+ - Modify: `templates/agent-instructions.md`
770
+ - Modify: `templates/memory-long-term.md`
771
+
772
+ **Interfaces:**
773
+ - Consumes: command behavior from Tasks 1-6.
774
+ - Produces: user-facing guidance for remember, recall, forget, improve, and events.
775
+
776
+ - [ ] **Step 1: Write docs smoke test**
777
+
778
+ Add this test near existing init/help tests:
779
+
780
+ ```js
781
+ test('documentation mentions local memory operations', () => {
782
+ const cliDocs = fs.readFileSync(path.join(repoRootForTests(), 'docs', 'cli.md'), 'utf8');
783
+ const memoryDocs = fs.readFileSync(path.join(repoRootForTests(), 'docs', 'memory.md'), 'utf8');
784
+ const agentTemplate = fs.readFileSync(path.join(repoRootForTests(), 'templates', 'agent-instructions.md'), 'utf8');
785
+
786
+ assert.match(cliDocs, /awareness remember/);
787
+ assert.match(cliDocs, /awareness recall/);
788
+ assert.match(cliDocs, /awareness forget/);
789
+ assert.match(cliDocs, /awareness improve/);
790
+ assert.match(memoryDocs, /memory\/events\.jsonl/);
791
+ assert.match(agentTemplate, /awareness recall/);
792
+ });
793
+ ```
794
+
795
+ Add this helper near `tempHome()`:
796
+
797
+ ```js
798
+ function repoRootForTests() {
799
+ return path.resolve(new URL('..', import.meta.url).pathname);
800
+ }
801
+ ```
802
+
803
+ - [ ] **Step 2: Run docs test to verify it fails**
804
+
805
+ Run: `npm test -- --test-name-pattern "documentation mentions local memory operations"`
806
+
807
+ Expected: FAIL because docs do not yet mention all four top-level operations.
808
+
809
+ - [ ] **Step 3: Update `docs/cli.md`**
810
+
811
+ In the `memory` section, add:
812
+
813
+ ````markdown
814
+ ### Local memory operations
815
+
816
+ These commands provide a small Cognee-inspired operation vocabulary without adding a graph database or vector store.
817
+
818
+ ```bash
819
+ awareness remember --text "Prefer recall before repeating implementation work" --evidence "User request"
820
+ awareness recall "implementation work"
821
+ awareness forget --text "Old assumption" --reason "Superseded by user correction" --evidence "Correction message"
822
+ awareness improve
823
+ ```
824
+
825
+ `remember` records a promotion candidate and appends `memory.candidate.created` to `memory/events.jsonl`.
826
+ `recall` performs deterministic local text search across memory, memory events, worklogs, and evaluations.
827
+ `forget` records a prune/revision entry and appends `memory.pruned`; it does not destructively delete historical evidence.
828
+ `improve` runs the evaluation/review loop and appends `evaluation.created` and `pattern.suggested` events when applicable.
829
+ ````
830
+
831
+ - [ ] **Step 4: Update `docs/memory.md`**
832
+
833
+ Add a section after "Memory Layers":
834
+
835
+ ````markdown
836
+ ## Local Operation Model
837
+
838
+ Awareness uses a small local operation vocabulary:
839
+
840
+ - `remember`: capture an evidence-backed candidate.
841
+ - `recall`: search local memory, events, worklogs, and evaluations.
842
+ - `forget`: prune or revise stale memory without destructive deletion.
843
+ - `improve`: run evaluation plus memory review to surface repeated candidates.
844
+
845
+ The append-only event log lives at:
846
+
847
+ ```text
848
+ ~/.agents/memory/events.jsonl
849
+ ```
850
+
851
+ Markdown files remain the readable projection. The event log is the auditable history of memory operations.
852
+ ````
853
+
854
+ - [ ] **Step 5: Update README private layout and quick start**
855
+
856
+ In the README private layout under `memory/`, add:
857
+
858
+ ```text
859
+ events.jsonl
860
+ ```
861
+
862
+ In CLI Quick Start, add:
863
+
864
+ ```bash
865
+ awareness remember --text "Useful local observation" --evidence "Source"
866
+ awareness recall "local observation"
867
+ awareness improve
868
+ ```
869
+
870
+ - [ ] **Step 6: Update templates**
871
+
872
+ In `templates/agent-instructions.md`, add rules:
873
+
874
+ ```markdown
875
+ - Use `awareness remember` for explicit observations that should enter memory review.
876
+ - Use `awareness recall QUERY` before repeating uncertain or previously solved work.
877
+ - Use `awareness forget --text TEXT --reason REASON --evidence EVIDENCE` when memory is stale, wrong, or superseded.
878
+ - Use `awareness improve` after material work or process friction to run evaluation plus memory review.
879
+ ```
880
+
881
+ In `templates/memory-long-term.md`, add:
882
+
883
+ ```markdown
884
+ ## Event Log
885
+
886
+ - Append-only audit history: `memory/events.jsonl`
887
+ - Markdown sections are readable projections.
888
+ - Do not hand-edit event history.
889
+ ```
890
+
891
+ - [ ] **Step 7: Run docs test**
892
+
893
+ Run: `npm test -- --test-name-pattern "documentation mentions local memory operations"`
894
+
895
+ Expected: PASS.
896
+
897
+ - [ ] **Step 8: Run full tests**
898
+
899
+ Run: `npm test`
900
+
901
+ Expected: PASS.
902
+
903
+ - [ ] **Step 9: Commit**
904
+
905
+ ```bash
906
+ git add README.md docs/cli.md docs/memory.md templates/agent-instructions.md templates/memory-long-term.md test/cli.test.js
907
+ git commit -m "Document local memory operations"
908
+ ```
909
+
910
+ ---
911
+
912
+ ### Task 8: Final Verification and PR
913
+
914
+ **Files:**
915
+ - No code files expected unless verification finds a defect.
916
+
917
+ **Interfaces:**
918
+ - Consumes: all commits from Tasks 1-7.
919
+ - Produces: a reviewable PR for the local memory operation model.
920
+
921
+ - [ ] **Step 1: Run full test suite**
922
+
923
+ Run: `npm test`
924
+
925
+ Expected: PASS, including tests for:
926
+
927
+ ```text
928
+ help lists local memory operation commands
929
+ memory note and promote append auditable memory events
930
+ remember records a promotion candidate and event
931
+ recall searches memory, events, worklogs, and evaluations
932
+ forget records a pruned memory without deleting history
933
+ improve writes evaluation and surfaces repeated pattern suggestions
934
+ documentation mentions local memory operations
935
+ ```
936
+
937
+ - [ ] **Step 2: Manually smoke-test the commands against a temp home**
938
+
939
+ Run:
940
+
941
+ ```bash
942
+ tmp_home="$(mktemp -d)"
943
+ node bin/awareness.js init --home "$tmp_home"
944
+ node bin/awareness.js remember --home "$tmp_home" --text "Smoke memory operation" --evidence "Manual smoke test"
945
+ node bin/awareness.js recall --home "$tmp_home" "Smoke memory"
946
+ node bin/awareness.js improve --home "$tmp_home"
947
+ node bin/awareness.js forget --home "$tmp_home" --text "Smoke memory operation" --reason "Smoke test cleanup" --evidence "Manual smoke test"
948
+ ```
949
+
950
+ Expected:
951
+
952
+ ```text
953
+ remember prints "Remembered candidate"
954
+ recall prints at least one result from memory/long-term.md or memory/events.jsonl
955
+ improve prints evaluation and pattern suggestion counts
956
+ forget prints "Memory pruned or revised"
957
+ ```
958
+
959
+ - [ ] **Step 3: Inspect event log from smoke test**
960
+
961
+ Run:
962
+
963
+ ```bash
964
+ tail -n 20 "$tmp_home/memory/events.jsonl"
965
+ ```
966
+
967
+ Expected: JSON lines include `memory.candidate.created`, `evaluation.created`, and `memory.pruned`.
968
+
969
+ - [ ] **Step 4: Check worktree**
970
+
971
+ Run: `git status -sb`
972
+
973
+ Expected: clean branch with commits ready to push.
974
+
975
+ - [ ] **Step 5: Open PR**
976
+
977
+ Run:
978
+
979
+ ```bash
980
+ git push -u origin codex/local-memory-operations
981
+ gh pr create --draft --base main --head codex/local-memory-operations --title "[codex] Add local memory operations" --body-file /private/tmp/local-memory-operations-pr.md
982
+ ```
983
+
984
+ Use this PR body:
985
+
986
+ ```markdown
987
+ ## Summary
988
+
989
+ Adds a small local-first memory operation model inspired by Cognee without adding graph, vector, or service dependencies.
990
+
991
+ - Adds `remember`, `recall`, `forget`, and `improve`.
992
+ - Adds append-only `memory/events.jsonl` for auditable memory operations.
993
+ - Keeps Markdown files as human-readable projections.
994
+ - Documents how agents should use the new operations.
995
+
996
+ ## Validation
997
+
998
+ - `npm test`
999
+ - Manual temp-home smoke test for remember/recall/improve/forget
1000
+ ```
1001
+
1002
+ - [ ] **Step 6: Request review before merge**
1003
+
1004
+ After PR creation, report:
1005
+
1006
+ ```text
1007
+ PR URL
1008
+ branch name
1009
+ commit list
1010
+ test result
1011
+ manual smoke result
1012
+ ```
1013
+
1014
+ Do not merge until the user explicitly approves.
1015
+
1016
+ ---
1017
+
1018
+ ## Self-Review
1019
+
1020
+ **Spec coverage:** The plan covers the approved design: operation vocabulary, append-only event log, Markdown projections, deterministic recall, non-destructive forget, improve as evaluation plus memory review, docs/templates, tests, and PR flow.
1021
+
1022
+ **Placeholder scan:** The plan contains no `TBD`, `TODO`, or unspecified implementation steps. Each code-changing step includes concrete code snippets and exact file paths.
1023
+
1024
+ **Type consistency:** Function names are consistent across tasks: `appendMemoryEvent`, `readMemoryEvents`, `memoryEventPath`, `rememberCommand`, `recallCommand`, `forgetCommand`, `improveCommand`, `collectRecallSources`, `recallMatches`.
1025
+
1026
+ **Scope check:** The plan avoids graph storage, embeddings, external services, new dependencies, and UI changes.